diff --git a/Bolts.framework/Bolts b/Bolts.framework/Bolts deleted file mode 100644 index 920ef27..0000000 Binary files a/Bolts.framework/Bolts and /dev/null differ diff --git a/Bolts.framework/Headers/BFAppLink.h b/Bolts.framework/Headers/BFAppLink.h deleted file mode 100644 index 5e51acd..0000000 --- a/Bolts.framework/Headers/BFAppLink.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -/*! The version of the App Link protocol that this library supports */ -FOUNDATION_EXPORT NSString *const BFAppLinkVersion; - -/*! - Contains App Link metadata relevant for navigation on this device - derived from the HTML at a given URL. - */ -@interface BFAppLink : NSObject - -/*! - Creates a BFAppLink with the given list of BFAppLinkTargets and target URL. - - Generally, this will only be used by implementers of the BFAppLinkResolving protocol, - as these implementers will produce App Link metadata for a given URL. - - @param sourceURL the URL from which this App Link is derived - @param targets an ordered list of BFAppLinkTargets for this platform derived - from App Link metadata. - @param webURL the fallback web URL, if any, for the app link. - */ -+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL - targets:(NSArray *)targets - webURL:(NSURL *)webURL; - -/*! The URL from which this BFAppLink was derived */ -@property (nonatomic, strong, readonly) NSURL *sourceURL; - -/*! - The ordered list of targets applicable to this platform that will be used - for navigation. - */ -@property (nonatomic, copy, readonly) NSArray *targets; - -/*! The fallback web URL to use if no targets are installed on this device. */ -@property (nonatomic, strong, readonly) NSURL *webURL; - -@end diff --git a/Bolts.framework/Headers/BFAppLinkNavigation.h b/Bolts.framework/Headers/BFAppLinkNavigation.h deleted file mode 100644 index d459f72..0000000 --- a/Bolts.framework/Headers/BFAppLinkNavigation.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -#import - -/*! - The result of calling navigate on a BFAppLinkNavigation - */ -typedef NS_ENUM(NSInteger, BFAppLinkNavigationType) { - /*! Indicates that the navigation failed and no app was opened */ - BFAppLinkNavigationTypeFailure, - /*! Indicates that the navigation succeeded by opening the URL in the browser */ - BFAppLinkNavigationTypeBrowser, - /*! Indicates that the navigation succeeded by opening the URL in an app on the device */ - BFAppLinkNavigationTypeApp -}; - -@protocol BFAppLinkResolving; -@class BFTask; - -/*! - Represents a pending request to navigate to an App Link. Most developers will - simply use navigateToURLInBackground: to open a URL, but developers can build - custom requests with additional navigation and app data attached to them by - creating BFAppLinkNavigations themselves. - */ -@interface BFAppLinkNavigation : NSObject - -/*! - The extras for the AppLinkNavigation. This will generally contain application-specific - data that should be passed along with the request, such as advertiser or affiliate IDs or - other such metadata relevant on this device. - */ -@property (nonatomic, copy, readonly) NSDictionary *extras; - -/*! - The al_applink_data for the AppLinkNavigation. This will generally contain data common to - navigation attempts such as back-links, user agents, and other information that may be used - in routing and handling an App Link request. - */ -@property (nonatomic, copy, readonly) NSDictionary *appLinkData; - -/*! The AppLink to navigate to */ -@property (nonatomic, strong, readonly) BFAppLink *appLink; - -/*! Creates an AppLinkNavigation with the given link, extras, and App Link data */ -+ (instancetype)navigationWithAppLink:(BFAppLink *)appLink - extras:(NSDictionary *)extras - appLinkData:(NSDictionary *)appLinkData; - -/*! Performs the navigation */ -- (BFAppLinkNavigationType)navigate:(NSError **)error; - -/*! Returns a BFAppLink for the given URL */ -+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination; - -/*! Returns a BFAppLink for the given URL using the given App Link resolution strategy */ -+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id)resolver; - -/*! Navigates to a BFAppLink and returns whether it opened in-app or in-browser */ -+ (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error; - -/*! Navigates to a URL (an asynchronous action) and returns a BFNavigationType */ -+ (BFTask *)navigateToURLInBackground:(NSURL *)destination; - -/*! - Navigates to a URL (an asynchronous action) using the given App Link resolution - strategy and returns a BFNavigationType - */ -+ (BFTask *)navigateToURLInBackground:(NSURL *)destination resolver:(id)resolver; - -/*! - Gets the default resolver to be used for App Link resolution. If the developer has not set one explicitly, - a basic, built-in resolver will be used. - */ -+ (id)defaultResolver; - -/*! - Sets the default resolver to be used for App Link resolution. Setting this to nil will revert the - default resolver to the basic, built-in resolver provided by Bolts. - */ -+ (void)setDefaultResolver:(id)resolver; - -@end diff --git a/Bolts.framework/Headers/BFAppLinkResolving.h b/Bolts.framework/Headers/BFAppLinkResolving.h deleted file mode 100644 index baa1451..0000000 --- a/Bolts.framework/Headers/BFAppLinkResolving.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -@class BFTask; - -/*! - Implement this protocol to provide an alternate strategy for resolving - App Links that may include pre-fetching, caching, or querying for App Link - data from an index provided by a service provider. - */ -@protocol BFAppLinkResolving - -/*! - Asynchronously resolves App Link data for a given URL. - - @param url The URL to resolve into an App Link. - @returns A BFTask that will return a BFAppLink for the given URL. - */ -- (BFTask *)appLinkFromURLInBackground:(NSURL *)url; - -@end diff --git a/Bolts.framework/Headers/BFAppLinkReturnToRefererController.h b/Bolts.framework/Headers/BFAppLinkReturnToRefererController.h deleted file mode 100644 index 22648d4..0000000 --- a/Bolts.framework/Headers/BFAppLinkReturnToRefererController.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import -#import - -#import - -@class BFAppLink; -@class BFAppLinkReturnToRefererController; - -/*! - Protocol that a class can implement in order to be notified when the user has navigated back - to the referer of an App Link. - */ -@protocol BFAppLinkReturnToRefererControllerDelegate - -@optional - -/*! Called when the user has tapped to navigate, but before the navigation has been performed. */ -- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller - willNavigateToAppLink:(BFAppLink *)appLink; - -/*! Called after the navigation has been attempted, with an indication of whether the referer - app link was successfully opened. */ -- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller - didNavigateToAppLink:(BFAppLink *)url - type:(BFAppLinkNavigationType)type; - -@end - -/*! - A controller class that implements default behavior for a BFAppLinkReturnToRefererView, including - the ability to display the view above the navigation bar for navigation-bsaed apps. - */ -@interface BFAppLinkReturnToRefererController : NSObject - -/*! - The delegate that will be notified when the user navigates back to the referer. - */ -@property (nonatomic, weak) id delegate; - -/*! - The BFAppLinkReturnToRefererView this controller is controlling. - */ -@property (nonatomic, strong) BFAppLinkReturnToRefererView *view; - -/*! - Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed - contained within another UIView (i.e., not displayed above the navigation bar). - */ -- (instancetype)init; - -/*! - Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed - displayed above the navigation bar. - */ -- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController; - -/*! - Removes the view entirely from the navigation controller it is currently displayed in. - */ -- (void)removeFromNavController; - -/*! - Shows the BFAppLinkReturnToRefererView with the specified referer information. If nil or missing data, - the view will not be displayed. */ -- (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink; - -/*! - Shows the BFAppLinkReturnToRefererView with referer information extracted from the specified URL. - If nil or missing referer App Link data, the view will not be displayed. */ -- (void)showViewForRefererURL:(NSURL *)url; - -/*! - Closes the view, possibly animating it. - */ -- (void)closeViewAnimated:(BOOL)animated; - -@end diff --git a/Bolts.framework/Headers/BFAppLinkReturnToRefererView.h b/Bolts.framework/Headers/BFAppLinkReturnToRefererView.h deleted file mode 100644 index e3af940..0000000 --- a/Bolts.framework/Headers/BFAppLinkReturnToRefererView.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import -#import - -#import - -@class BFAppLinkReturnToRefererView; -@class BFURL; - -typedef enum BFIncludeStatusBarInSize { - BFIncludeStatusBarInSizeNever, - BFIncludeStatusBarInSizeIOS7AndLater, - BFIncludeStatusBarInSizeAlways, -} BFIncludeStatusBarInSize; - -/*! - Protocol that a class can implement in order to be notified when the user has navigated back - to the referer of an App Link. - */ -@protocol BFAppLinkReturnToRefererViewDelegate - -/*! - Called when the user has tapped inside the close button. - */ -- (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view; - -/*! - Called when the user has tapped inside the App Link portion of the view. - */ -- (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view - link:(BFAppLink *)link; - -@end - -/*! - Provides a UIView that displays a button allowing users to navigate back to the - application that launched the App Link currently being handled, if the App Link - contained referer data. The user can also close the view by clicking a close button - rather than navigating away. If the view is provided an App Link that does not contain - referer data, it will have zero size and no UI will be displayed. - */ -@interface BFAppLinkReturnToRefererView : UIView - -/*! - The delegate that will be notified when the user navigates back to the referer. - */ -@property (nonatomic, weak) id delegate; - -/*! - The color of the text label and close button. - */ -@property (nonatomic, strong) UIColor *textColor; - -@property (nonatomic, strong) BFAppLink *refererAppLink; - -/*! - Indicates whether to extend the size of the view to include the current status bar - size, for use in scenarios where the view might extend under the status bar on iOS 7 and - above; this property has no effect on earlier versions of iOS. - */ -@property (nonatomic, assign) BFIncludeStatusBarInSize includeStatusBarInSize; - -/*! - Indicates whether the user has closed the view by clicking the close button. - */ -@property (nonatomic, assign) BOOL closed; - -/*! - For apps that use a navigation controller, this method allows for displaying the view as - a banner above the navigation bar of the navigation controller. It will listen for orientation - change and other events to ensure it stays properly positioned above the nevigation bar. - If this method is called from, e.g., viewDidAppear, its counterpart, detachFromMainWindow should - be called from, e.g., viewWillDisappear. - */ -//- (void)attachToMainWindowAboveNavigationController:(UINavigationController *)navigationController view:(UIView *)view; - -/*! - Indicates that the view should no longer position itself above a navigation bar. - */ -//- (void)detachFromMainWindow; - -@end diff --git a/Bolts.framework/Headers/BFAppLinkTarget.h b/Bolts.framework/Headers/BFAppLinkTarget.h deleted file mode 100644 index 6172126..0000000 --- a/Bolts.framework/Headers/BFAppLinkTarget.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -/*! - Represents a target defined in App Link metadata, consisting of at least - a URL, and optionally an App Store ID and name. - */ -@interface BFAppLinkTarget : NSObject - -/*! Creates a BFAppLinkTarget with the given app site and target URL. */ -+ (instancetype)appLinkTargetWithURL:(NSURL *)url - appStoreId:(NSString *)appStoreId - appName:(NSString *)appName; - -/*! The URL prefix for this app link target */ -@property (nonatomic, strong, readonly) NSURL *URL; - -/*! The app ID for the app store */ -@property (nonatomic, copy, readonly) NSString *appStoreId; - -/*! The name of the app */ -@property (nonatomic, copy, readonly) NSString *appName; - -@end diff --git a/Bolts.framework/Headers/BFExecutor.h b/Bolts.framework/Headers/BFExecutor.h deleted file mode 100644 index 02af9ba..0000000 --- a/Bolts.framework/Headers/BFExecutor.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -/*! - An object that can run a given block. - */ -@interface BFExecutor : NSObject - -/*! - Returns a default executor, which runs continuations immediately until the call stack gets too - deep, then dispatches to a new GCD queue. - */ -+ (instancetype)defaultExecutor; - -/*! - Returns an executor that runs continuations on the thread where the previous task was completed. - */ -+ (instancetype)immediateExecutor; - -/*! - Returns an executor that runs continuations on the main thread. - */ -+ (instancetype)mainThreadExecutor; - -/*! - Returns a new executor that uses the given block to execute continuations. - @param block The block to use. - */ -+ (instancetype)executorWithBlock:(void(^)(void(^block)()))block; - -/*! - Returns a new executor that runs continuations on the given queue. - @param queue The instance of `dispatch_queue_t` to dispatch all continuations onto. - */ -+ (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue; - -/*! - Returns a new executor that runs continuations on the given queue. - @param queue The instance of `NSOperationQueue` to run all continuations on. - */ -+ (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue; - -/*! - Runs the given block using this executor's particular strategy. - @param block The block to execute. - */ -- (void)execute:(void(^)())block; - -@end diff --git a/Bolts.framework/Headers/BFMeasurementEvent.h b/Bolts.framework/Headers/BFMeasurementEvent.h deleted file mode 100644 index 7a9948c..0000000 --- a/Bolts.framework/Headers/BFMeasurementEvent.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -/*! The name of the notification posted by BFMeasurementEvent */ -FOUNDATION_EXPORT NSString *const BFMeasurementEventNotificationName; - -/*! Defines keys in the userInfo object for the notification named BFMeasurementEventNotificationName */ -/*! The string field for the name of the event */ -FOUNDATION_EXPORT NSString *const BFMeasurementEventNameKey; -/*! The dictionary field for the arguments of the event */ -FOUNDATION_EXPORT NSString *const BFMeasurementEventArgsKey; - - -/*! Bolts Events raised by BFMeasurementEvent for Applink */ -/*! - The name of the event posted when [BFURL URLWithURL:] is called successfully. This represents the successful parsing of an app link URL. - */ -FOUNDATION_EXPORT NSString *const BFAppLinkParseEventName; - -/*! - The name of the event posted when [BFURL URLWithInboundURL:] is called successfully. - This represents parsing an inbound app link URL from a different application - */ -FOUNDATION_EXPORT NSString *const BFAppLinkNavigateInEventName; - -/*! The event raised when the user navigates from your app to other apps */ -FOUNDATION_EXPORT NSString *const BFAppLinkNavigateOutEventName; - -/*! - The event raised when the user navigates out from your app and back to the referrer app. - e.g when the user leaves your app after tapping the back-to-referrer navigation bar - */ -FOUNDATION_EXPORT NSString *const BFAppLinkNavigateBackToReferrerEventName; - -@interface BFMeasurementEvent : NSObject - -@end diff --git a/Bolts.framework/Headers/BFTask.h b/Bolts.framework/Headers/BFTask.h deleted file mode 100644 index ce8db4f..0000000 --- a/Bolts.framework/Headers/BFTask.h +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -/*! - Error domain used if there was multiple errors on . - */ -extern NSString *const BFTaskErrorDomain; - -/*! - An exception that is thrown if there was multiple exceptions on . - */ -extern NSString *const BFTaskMultipleExceptionsException; - -@class BFExecutor; -@class BFTask; - -/*! - A block that can act as a continuation for a task. - */ -typedef id(^BFContinuationBlock)(BFTask *task); - -/*! - The consumer view of a Task. A BFTask has methods to - inspect the state of the task, and to add continuations to - be run once the task is complete. - */ -@interface BFTask : NSObject - -/*! - Creates a task that is already completed with the given result. - @param result The result for the task. - */ -+ (instancetype)taskWithResult:(id)result; - -/*! - Creates a task that is already completed with the given error. - @param error The error for the task. - */ -+ (instancetype)taskWithError:(NSError *)error; - -/*! - Creates a task that is already completed with the given exception. - @param exception The exception for the task. - */ -+ (instancetype)taskWithException:(NSException *)exception; - -/*! - Creates a task that is already cancelled. - */ -+ (instancetype)cancelledTask; - -/*! - Returns a task that will be completed (with result == nil) once - all of the input tasks have completed. - @param tasks An `NSArray` of the tasks to use as an input. - */ -+ (instancetype)taskForCompletionOfAllTasks:(NSArray *)tasks; - -/*! - Returns a task that will be completed once all of the input tasks have completed. - If all tasks complete successfully without being faulted or cancelled the result will be - an `NSArray` of all task results in the order they were provided. - @param tasks An `NSArray` of the tasks to use as an input. - */ -+ (instancetype)taskForCompletionOfAllTasksWithResults:(NSArray *)tasks; - -/*! - Returns a task that will be completed a certain amount of time in the future. - @param millis The approximate number of milliseconds to wait before the - task will be finished (with result == nil). - */ -+ (instancetype)taskWithDelay:(int)millis; - -/*! - Returns a task that will be completed after the given block completes with - the specified executor. - @param executor A BFExecutor responsible for determining how the - continuation block will be run. - @param block The block to immediately schedule to run with the given executor. - @returns A task that will be completed after block has run. - If block returns a BFTask, then the task returned from - this method will not be completed until that task is completed. - */ -+ (instancetype)taskFromExecutor:(BFExecutor *)executor - withBlock:(id (^)())block; - -// Properties that will be set on the task once it is completed. - -/*! - The result of a successful task. - */ -@property (nonatomic, strong, readonly) id result; - - -/*! - The error of a failed task. - */ -@property (nonatomic, strong, readonly) NSError *error; - -/*! - The exception of a failed task. - */ -@property (nonatomic, strong, readonly) NSException *exception; - -/*! - Whether this task has been cancelled. - */ -@property (nonatomic, assign, readonly, getter = isCancelled) BOOL cancelled; - -/*! - Whether this task has completed due to an error or exception. - */ -@property (nonatomic, assign, readonly, getter = isFaulted) BOOL faulted; - -/*! - Whether this task has completed. - */ -@property (nonatomic, assign, readonly, getter = isCompleted) BOOL completed; - -/*! - Enqueues the given block to be run once this task is complete. - This method uses a default execution strategy. The block will be - run on the thread where the previous task completes, unless the - the stack depth is too deep, in which case it will be run on a - dispatch queue with default priority. - @param block The block to be run once this task is complete. - @returns A task that will be completed after block has run. - If block returns a BFTask, then the task returned from - this method will not be completed until that task is completed. - */ -- (instancetype)continueWithBlock:(BFContinuationBlock)block; - -/*! - Enqueues the given block to be run once this task is complete. - @param executor A BFExecutor responsible for determining how the - continuation block will be run. - @param block The block to be run once this task is complete. - @returns A task that will be completed after block has run. - If block returns a BFTask, then the task returned from - this method will not be completed until that task is completed. - */ -- (instancetype)continueWithExecutor:(BFExecutor *)executor - withBlock:(BFContinuationBlock)block; - -/*! - Identical to continueWithBlock:, except that the block is only run - if this task did not produce a cancellation, error, or exception. - If it did, then the failure will be propagated to the returned - task. - @param block The block to be run once this task is complete. - @returns A task that will be completed after block has run. - If block returns a BFTask, then the task returned from - this method will not be completed until that task is completed. - */ -- (instancetype)continueWithSuccessBlock:(BFContinuationBlock)block; - -/*! - Identical to continueWithExecutor:withBlock:, except that the block - is only run if this task did not produce a cancellation, error, or - exception. If it did, then the failure will be propagated to the - returned task. - @param executor A BFExecutor responsible for determining how the - continuation block will be run. - @param block The block to be run once this task is complete. - @returns A task that will be completed after block has run. - If block returns a BFTask, then the task returned from - this method will not be completed until that task is completed. - */ -- (instancetype)continueWithExecutor:(BFExecutor *)executor - withSuccessBlock:(BFContinuationBlock)block; - -/*! - Waits until this operation is completed. - This method is inefficient and consumes a thread resource while - it's running. It should be avoided. This method logs a warning - message if it is used on the main thread. - */ -- (void)waitUntilFinished; - -@end diff --git a/Bolts.framework/Headers/BFTaskCompletionSource.h b/Bolts.framework/Headers/BFTaskCompletionSource.h deleted file mode 100644 index d0ea545..0000000 --- a/Bolts.framework/Headers/BFTaskCompletionSource.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -@class BFTask; - -/*! - A BFTaskCompletionSource represents the producer side of tasks. - It is a task that also has methods for changing the state of the - task by settings its completion values. - */ -@interface BFTaskCompletionSource : NSObject - -/*! - Creates a new unfinished task. - */ -+ (instancetype)taskCompletionSource; - -/*! - The task associated with this TaskCompletionSource. - */ -@property (nonatomic, retain, readonly) BFTask *task; - -/*! - Completes the task by setting the result. - Attempting to set this for a completed task will raise an exception. - @param result The result of the task. - */ -- (void)setResult:(id)result; - -/*! - Completes the task by setting the error. - Attempting to set this for a completed task will raise an exception. - @param error The error for the task. - */ -- (void)setError:(NSError *)error; - -/*! - Completes the task by setting an exception. - Attempting to set this for a completed task will raise an exception. - @param exception The exception for the task. - */ -- (void)setException:(NSException *)exception; - -/*! - Completes the task by marking it as cancelled. - Attempting to set this for a completed task will raise an exception. - */ -- (void)cancel; - -/*! - Sets the result of the task if it wasn't already completed. - @returns whether the new value was set. - */ -- (BOOL)trySetResult:(id)result; - -/*! - Sets the error of the task if it wasn't already completed. - @param error The error for the task. - @returns whether the new value was set. - */ -- (BOOL)trySetError:(NSError *)error; - -/*! - Sets the exception of the task if it wasn't already completed. - @param exception The exception for the task. - @returns whether the new value was set. - */ -- (BOOL)trySetException:(NSException *)exception; - -/*! - Sets the cancellation state of the task if it wasn't already completed. - @returns whether the new value was set. - */ -- (BOOL)trySetCancelled; - -@end diff --git a/Bolts.framework/Headers/BFURL.h b/Bolts.framework/Headers/BFURL.h deleted file mode 100644 index f269e2d..0000000 --- a/Bolts.framework/Headers/BFURL.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - - - -@class BFAppLink; - -/*! - Provides a set of utilities for working with NSURLs, such as parsing of query parameters - and handling for App Link requests. - */ -@interface BFURL : NSObject - -/*! - Creates a link target from a raw URL. - On success, this posts the BFAppLinkParseEventName measurement event. If you are constructing the BFURL within your application delegate's - application:openURL:sourceApplication:annotation:, you should instead use URLWithInboundURL:sourceApplication: - to support better BFMeasurementEvent notifications - @param url The instance of `NSURL` to create BFURL from. - */ -+ (BFURL *)URLWithURL:(NSURL *)url; - -/*! - Creates a link target from a raw URL received from an external application. This is typically called from the app delegate's - application:openURL:sourceApplication:annotation: and will post the BFAppLinkNavigateInEventName measurement event. - @param url The instance of `NSURL` to create BFURL from. - @param sourceApplication the bundle ID of the app that is requesting your app to open the URL. The same sourceApplication in application:openURL:sourceApplication:annotation: -*/ -+ (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication; - -/*! - Gets the target URL. If the link is an App Link, this is the target of the App Link. - Otherwise, it is the url that created the target. - */ -@property (nonatomic, strong, readonly) NSURL *targetURL; - -/*! - Gets the query parameters for the target, parsed into an NSDictionary. - */ -@property (nonatomic, strong, readonly) NSDictionary *targetQueryParameters; - -/*! - If this link target is an App Link, this is the data found in al_applink_data. - Otherwise, it is nil. - */ -@property (nonatomic, strong, readonly) NSDictionary *appLinkData; - -/*! - If this link target is an App Link, this is the data found in extras. - */ -@property (nonatomic, strong, readonly) NSDictionary *appLinkExtras; - -/*! - The App Link indicating how to navigate back to the referer app, if any. - */ -@property (nonatomic, strong, readonly) BFAppLink *appLinkReferer; - -/*! - The URL that was used to create this BFURL. - */ -@property (nonatomic, strong, readonly) NSURL *inputURL; - -/*! - The query parameters of the inputURL, parsed into an NSDictionary. - */ -@property (nonatomic, strong, readonly) NSDictionary *inputQueryParameters; - -@end diff --git a/Bolts.framework/Headers/BFWebViewAppLinkResolver.h b/Bolts.framework/Headers/BFWebViewAppLinkResolver.h deleted file mode 100644 index cffa529..0000000 --- a/Bolts.framework/Headers/BFWebViewAppLinkResolver.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import - -#import - -/*! - A reference implementation for an App Link resolver that uses a hidden UIWebView - to parse the HTML containing App Link metadata. - */ -@interface BFWebViewAppLinkResolver : NSObject - -/*! - Gets the instance of a BFWebViewAppLinkResolver. - */ -+ (instancetype)sharedInstance; - -@end diff --git a/Bolts.framework/Headers/Bolts.h b/Bolts.framework/Headers/Bolts.h deleted file mode 100644 index e22bdd5..0000000 --- a/Bolts.framework/Headers/Bolts.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -#import -#import -#import -#import - -#if TARGET_OS_IPHONE -#import -#import -#import -#import -#import -#import -#import -#endif - -/*! @abstract 80175001: There were multiple errors. */ -extern NSInteger const kBFMultipleErrorsError; - -@interface Bolts : NSObject - -/*! - Returns the version of the Bolts Framework as an NSString. - @returns The NSString representation of the current version. - */ -+ (NSString *)version; - -@end diff --git a/Bolts.framework/Headers/BoltsVersion.h b/Bolts.framework/Headers/BoltsVersion.h deleted file mode 100644 index 866d954..0000000 --- a/Bolts.framework/Headers/BoltsVersion.h +++ /dev/null @@ -1 +0,0 @@ -#define BOLTS_VERSION @"1.1.4" diff --git a/Bolts.framework/Info.plist b/Bolts.framework/Info.plist deleted file mode 100644 index f73e252..0000000 Binary files a/Bolts.framework/Info.plist and /dev/null differ diff --git a/Bolts.framework/Modules/module.modulemap b/Bolts.framework/Modules/module.modulemap deleted file mode 100644 index 1da9155..0000000 --- a/Bolts.framework/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Bolts { - umbrella header "Bolts.h" - - export * - module * { export * } -} diff --git a/CLAUDE.md b/CLAUDE.md index a9d6a19..d2cb953 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,8 +4,8 @@ **vBox** is an iOS application for vehicle trip logging that supports integration with OBD-II (On-Board Diagnostics) adapters via Bluetooth Low Energy (BLE). The app tracks driving metrics including GPS location, speed, distance, and when connected to a compatible OBD adapter, retrieves real-time vehicle diagnostics such as RPM, fuel level, engine load, and temperatures. -- **Platform**: iOS (minimum iOS 8.1) -- **Language**: Objective-C +- **Platform**: iOS 13.0+ (required for Combine framework) +- **Languages**: Swift 5.0+ (modern) / Objective-C (legacy, being migrated) - **License**: MIT - **Author**: Rosbel Sanroman @@ -14,72 +14,134 @@ ``` vBoxiOS/ ├── vBox/ # Main application source code -│ ├── AppDelegate.{h,m} # App lifecycle, Core Data stack, Push notifications -│ ├── BLEManager.{h,m} # Bluetooth Low Energy manager for OBD communication -│ ├── GoogleMapsViewController.{h,m} # Main driving view with map and gauges -│ ├── MainScreenViewController.{h,m} # Home screen -│ ├── TripDetailViewController.{h,m} # Trip playback and review -│ ├── DrivingHistoryViewController.{h,m} # List of recorded trips -│ ├── BluetoothTableViewController.{h,m} # BLE device selection -│ ├── DebugBluetoothViewController.{h,m} # BLE debugging interface +│ ├── AppDelegate.{h,m} # App lifecycle, Core Data stack │ │ -│ ├── # Data Models (Core Data NSManagedObject subclasses) -│ ├── Trip.{h,m} # Trip entity with metadata -│ ├── GPSLocation.{h,m} # GPS coordinate entity -│ ├── BluetoothData.{h,m} # OBD diagnostic data entity -│ ├── DrivingHistory.{h,m} # Root container for trips +│ ├── # Swift Models (Modern) +│ ├── Models/ +│ │ ├── OBDTypes.swift # OBD-II protocol types (PIDs, packets, diagnostics) +│ │ ├── BLETypes.swift # BLE state, connection types, errors +│ │ └── CoreData/ # Swift Core Data entity classes +│ │ ├── TripEntity.swift +│ │ ├── GPSLocationEntity.swift +│ │ ├── BluetoothDataEntity.swift +│ │ └── DrivingHistoryEntity.swift │ │ -│ ├── # Utilities & Third-Party -│ ├── UtilityMethods.{h,m} # Date formatting helpers -│ ├── WMGaugeView.{h,m} # Gauge UI component (third-party) -│ ├── OBSlider.{h,m} # Scrubber slider (third-party) -│ ├── SVProgressHUD.{h,m} # Progress indicator (third-party) -│ ├── MyStyleKit.{h,m} # PaintCode-generated drawing code +│ ├── # Swift Managers (Modern) +│ ├── Managers/ +│ │ └── BLEManagerSwift.swift # Modern BLE manager with Combine │ │ -│ ├── # Resources -│ ├── Base.lproj/Main.storyboard # Main UI storyboard -│ ├── Base.lproj/LaunchScreen.xib # Launch screen -│ ├── GPSInformation.xcdatamodeld/ # Core Data model -│ ├── Images.xcassets/ # Image assets -│ ├── Settings.bundle/ # iOS Settings integration -│ └── Info.plist # App configuration +│ ├── # Swift Utilities (Modern) +│ ├── Utilities/ +│ │ └── DateFormatting.swift # Duration and date formatting +│ │ +│ ├── # Swift View Controllers (Modern) +│ ├── ViewControllers/ +│ │ ├── GoogleMapsViewController.swift # Trip recording with map +│ │ ├── MainScreenViewController.swift # Home screen navigation +│ │ ├── TripDetailViewController.swift # Trip playback with scrubber +│ │ ├── DrivingHistoryViewController.swift # Trip list by date +│ │ ├── BluetoothTableViewController.swift # OBD diagnostic display +│ │ └── DebugBluetoothViewController.swift # BLE debug console +│ │ +│ ├── # Objective-C Legacy (for reference) +│ ├── BLEManager.{h,m} # Legacy BLE manager +│ ├── GoogleMapsViewController.{h,m} # Legacy - see ViewControllers/ +│ ├── MainScreenViewController.{h,m} # Legacy - see ViewControllers/ +│ ├── TripDetailViewController.{h,m} # Legacy - see ViewControllers/ +│ ├── DrivingHistoryViewController.{h,m} # Legacy - see ViewControllers/ +│ ├── BluetoothTableViewController.{h,m} # Legacy - see ViewControllers/ +│ ├── DebugBluetoothViewController.{h,m} # Legacy - see ViewControllers/ +│ │ +│ ├── # Legacy Data Models (Objective-C) +│ ├── Trip.{h,m}, GPSLocation.{h,m}, BluetoothData.{h,m}, DrivingHistory.{h,m} +│ │ +│ ├── # Third-Party Libraries +│ ├── WMGaugeView.{h,m} # Gauge UI component +│ ├── OBSlider.{h,m} # Scrubber slider +│ ├── SVProgressHUD.{h,m} # Progress indicator +│ ├── MyStyleKit.{h,m} # PaintCode drawings +│ │ +│ ├── # Bridging Headers +│ ├── vBox-Bridging-Header.h # Swift/Obj-C interop +│ │ +│ └── # Resources +│ ├── Base.lproj/Main.storyboard +│ ├── GPSInformation.xcdatamodeld/ +│ ├── Images.xcassets/ +│ └── Info.plist +│ +├── vBoxTests/ # Unit tests (Swift) +│ ├── OBDTypesTests.swift # OBD protocol tests +│ ├── BLETypesTests.swift # BLE types tests +│ ├── BLEManagerSwiftTests.swift # BLE manager tests +│ ├── CoreDataModelTests.swift # Core Data tests +│ ├── DateFormattingTests.swift # Formatting tests +│ └── TestHelpers.swift # Test utilities │ -├── vBoxTests/ # Unit tests ├── Pods/ # CocoaPods dependencies -├── *.framework/ # Vendored frameworks (Parse, Bolts, ParseCrashReporting) -├── vBox.xcodeproj/ # Xcode project -├── vBox.xcworkspace/ # Xcode workspace (use this for development) -├── Podfile # CocoaPods dependency specification -└── Podfile.lock # Locked dependency versions +├── vBox.xcworkspace/ # Use this for development +├── Podfile # Dependencies +└── Podfile.lock ``` ## Architecture -### Core Components +### Modern Swift Components + +#### OBDTypes (`Models/OBDTypes.swift`) +- `OBDPID` enum - All supported OBD-II parameter IDs with validation +- `BLEDataPacket` - 12-byte packet parsing with checksum +- `DiagnosticReading` - Validated diagnostic value with display formatting +- `VehicleDiagnostics` - Snapshot of all vehicle data +- `AccelerometerReading` - 3-axis acceleration data + +#### BLETypes (`Models/BLETypes.swift`) +- `BLEState` - Bluetooth adapter states +- `PeripheralType` - OBD adapter vs BeagleBone +- `ConnectionState` - Connection lifecycle +- `DiscoveredPeripheral` - Discovered device info +- `SignalStrength` - RSSI categorization +- `BLEError` - Typed errors with descriptions + +#### BLEManagerSwift (`Managers/BLEManagerSwift.swift`) +- Modern Combine-based BLE manager +- Publishers: `$state`, `$isConnected`, `$diagnostics` +- Convenience publishers: `speedPublisher`, `rpmPublisher`, `fuelLevelPublisher` +- Thread-safe with dedicated dispatch queues +- Delegate protocol for Objective-C compatibility + +#### Core Data Swift Models (`Models/CoreData/`) +- `TripEntity` - Trip with computed duration, distance, path coordinates +- `GPSLocationEntity` - Location with CLLocation conversion, distance/bearing +- `BluetoothDataEntity` - OBD diagnostics with temperature monitoring +- `DrivingHistoryEntity` - Trip management and statistics + +#### Swift View Controllers (`ViewControllers/`) +| Controller | Purpose | +|------------|---------| +| `GoogleMapsViewControllerSwift` | Trip recording with GPS and BLE using Combine | +| `MainScreenViewControllerSwift` | Home screen with button styling | +| `TripDetailViewControllerSwift` | Trip playback with map, gauges, and scrubber | +| `DrivingHistoryViewControllerSwift` | Trip list grouped by date | +| `BluetoothTableViewControllerSwift` | OBD diagnostic display using Combine | +| `DebugBluetoothViewControllerSwift` | BLE debug console with Combine | -#### AppDelegate (`AppDelegate.{h,m}`) -- Manages the Core Data stack (NSManagedObjectContext, NSPersistentStoreCoordinator) -- Initializes Google Maps SDK and Parse SDK -- Handles push notification registration and processing -- Provides access to the singleton `DrivingHistory` object +### Legacy Objective-C Components -#### BLEManager (`BLEManager.{h,m}`) -- Singleton-style Bluetooth Low Energy manager -- Implements `CBCentralManagerDelegate` and `CBPeripheralDelegate` -- Scans for and connects to OBD adapters (service UUID: `FFE0`) -- Parses OBD-II PID data from the Freematics adapter -- Supports PIDs: Speed, RPM, Fuel, Coolant Temp, Engine Load, Throttle, Intake Temp, etc. -- Uses checksum validation for data integrity +#### AppDelegate +- Core Data stack management +- Google Maps SDK initialization +- Singleton `DrivingHistory` access -#### View Controllers +#### Legacy View Controllers (being replaced) | Controller | Purpose | |------------|---------| -| `MainScreenViewController` | App home screen with navigation buttons | -| `GoogleMapsViewController` | Live trip recording with map and diagnostic gauges | -| `TripDetailViewController` | Trip playback with timeline scrubber | -| `DrivingHistoryViewController` | Table view of all recorded trips | -| `BluetoothTableViewController` | BLE peripheral discovery and selection | -| `DebugBluetoothViewController` | Debug console for BLE communication | +| `GoogleMapsViewController` | Legacy - use Swift version | +| `MainScreenViewController` | Legacy - use Swift version | +| `TripDetailViewController` | Legacy - use Swift version | +| `DrivingHistoryViewController` | Legacy - use Swift version | +| `BluetoothTableViewController` | Legacy - use Swift version | +| `DebugBluetoothViewController` | Legacy - use Swift version | ### Data Model (Core Data) @@ -103,11 +165,6 @@ DrivingHistory (root) **CocoaPods (Podfile)**: - `GoogleMaps` - Maps SDK for iOS -**Vendored Frameworks**: -- `Parse.framework` - Backend-as-a-Service (deprecated) -- `Bolts.framework` - Task/promise library (Parse dependency) -- `ParseCrashReporting.framework` - Crash reporting - **Bundled Third-Party Libraries**: - `WMGaugeView` - Animated gauge visualization - `OBSlider` - Slider with scrubbing support @@ -117,7 +174,7 @@ DrivingHistory (root) ### Opening the Project ```bash -# Always use the workspace (not the .xcodeproj) for CocoaPods support +# Always use the workspace for CocoaPods support open vBox.xcworkspace ``` @@ -127,87 +184,109 @@ open vBox.xcworkspace 3. Choose a simulator or device target 4. Build with Cmd+B or Run with Cmd+R +### Running Tests +```bash +# Run all tests +xcodebuild test -workspace vBox.xcworkspace -scheme vBox -destination 'platform=iOS Simulator,name=iPhone 15' +``` + ### Dependencies ```bash -# Install CocoaPods if needed gem install cocoapods - -# Install/update dependencies pod install ``` -### Requirements -- Xcode (with iOS SDK) -- CocoaPods -- Google Maps API key (configured in AppDelegate.m) +### System Requirements +- iOS 13.0+ deployment target (for Combine framework support) +- Xcode 14+ (for Swift 5 and iOS 13+ SDK) +- CocoaPods 1.10+ ## Key Conventions -### Objective-C Style +### Swift Style (Modern Code) +- Use `@Published` properties with Combine for reactive updates +- Prefer value types (`struct`, `enum`) over classes where appropriate +- Use `async/await` for asynchronous operations +- Create factory methods on types (e.g., `TripEntity.create(in:)`) +- Add computed properties for common conversions + +### Objective-C Style (Legacy Code) - Properties use `@synthesize` with underscore prefix ivars - Delegate protocols defined in header files - `#pragma mark -` sections for code organization -- Async operations dispatch to main thread for UI updates ### Bluetooth Communication -- BLE operations run on dedicated serial queues (`bluetoothThread`, `peripheralThread`) +- BLE operations run on dedicated serial queues - Data validation via checksum before processing -- PID values defined as hex constants (e.g., `#define PID_RPM 0x10C`) -- Delegate pattern for communicating state changes +- PID values defined in `OBDPID` enum (Swift) or hex constants (Obj-C) ### Core Data - Main context uses `NSMainQueueConcurrencyType` - Automatic lightweight migration enabled - SQLite store at `Documents/GPSInformation.sqlite` +- Use Swift entity classes for new code -### Background Modes -The app requires these background capabilities (Info.plist): -- `bluetooth-central` - BLE communication -- `bluetooth-peripheral` - BLE advertising -- `location` - GPS tracking -- `remote-notification` - Push notifications +### Testing +- All new Swift code should have corresponding tests +- Use in-memory Core Data context for tests +- Test helpers in `TestHelpers.swift` ## Important Notes for AI Assistants ### Code Modifications -1. **Always use the workspace**: Changes must be made with `vBox.xcworkspace` context -2. **Objective-C patterns**: Follow existing delegate and category patterns -3. **Core Data migrations**: Adding model attributes requires a new model version -4. **API Keys**: The codebase contains hardcoded API keys for Google Maps and Parse - these should be moved to configuration files in production +1. **Prefer Swift**: Write new code in Swift, not Objective-C +2. **Use the workspace**: Always work with `vBox.xcworkspace` +3. **Add tests**: All new Swift code needs test coverage +4. **Core Data migrations**: Adding model attributes requires a new model version ### Testing Considerations - BLE functionality requires a physical device - GPS simulation available in Xcode for location testing - OBD adapter integration requires Freematics hardware - -### Deprecated Components -- **Parse SDK**: Parse shut down in 2017; this integration is non-functional -- Consider removing Parse dependencies and related code if modernizing +- Use `CoreDataTestCase` base class for database tests ### File Naming -- Header/implementation pairs: `ClassName.h` / `ClassName.m` -- Categories use `+` syntax: `UINavigationController+Orientation.{h,m}` +- Swift files: `TypeName.swift` +- Swift tests: `TypeNameTests.swift` +- Objective-C pairs: `ClassName.h` / `ClassName.m` - Core Data model: `GPSInformation.xcdatamodeld` ### OBD-II Protocol The BLEManager supports Freematics OBD adapters with a custom binary protocol: -- 12-byte packets with checksum validation -- PIDs map to standard OBD-II parameters -- Data structure: `{time, pid, flags, checksum, value[3]}` +- 12-byte packets with XOR checksum validation +- PIDs defined in `OBDPID` enum with display names and max values +- Data structure: `{time: UInt32, pid: UInt16, flags: UInt8, checksum: UInt8, value: Float}` ## Common Tasks -### Adding a New View Controller -1. Create `NewViewController.h` and `NewViewController.m` in `vBox/` -2. Add to Main.storyboard or create programmatically -3. Follow existing patterns for delegate protocols +### Adding a New Swift Type +1. Create `TypeName.swift` in appropriate directory +2. Create `TypeNameTests.swift` in `vBoxTests/` +3. Add to bridging header if needed for Obj-C access + +### Using the Modern BLE Manager +```swift +// Subscribe to diagnostics +BLEManagerSwift.shared.diagnosticsPublisher + .sink { diagnostics in + print("Speed: \(diagnostics.speed ?? 0)") + } + .store(in: &cancellables) + +// Start scanning +BLEManagerSwift.shared.scan(for: .obdAdapter) +``` + +### Working with Core Data in Swift +```swift +// Create a new trip +let trip = TripEntity.create(in: context) +trip.tripName = "Morning Commute" -### Adding a Core Data Entity -1. Open `GPSInformation.xcdatamodeld` -2. Add new model version (Editor > Add Model Version) -3. Create corresponding `NSManagedObject` subclass files +// Add a location +let location = GPSLocationEntity.create(in: context, from: clLocation) +trip.addToGpsLocations(location) -### Modifying BLE Data Handling -1. Add PID constant in `BLEManager.m` -2. Add case in `peripheral:didUpdateValueForCharacteristic:error:` -3. Call `asyncUpdateDiagnosticForKey:withValue:ifUnderLimit:` +// Calculate statistics +trip.calculateStatistics() +``` diff --git a/Parse.framework/Headers/PFACL.h b/Parse.framework/Headers/PFACL.h deleted file mode 100644 index 3582a1e..0000000 --- a/Parse.framework/Headers/PFACL.h +++ /dev/null @@ -1,265 +0,0 @@ -// -// PFACL.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class PFRole; -@class PFUser; - -/*! - The `PFACL` class is used to control which users can access or modify a particular object. - Each can have its own `PFACL`. You can grant read and write permissions separately to specific users, - to groups of users that belong to roles, or you can grant permissions to "the public" so that, - for example, any user could read a particular object but only a particular set of users could write to that object. - */ -@interface PFACL : NSObject - -///-------------------------------------- -/// @name Creating an ACL -///-------------------------------------- - -/*! - @abstract Creates an ACL with no permissions granted. - - @returns Returns a new `PFACL`. - */ -+ (PFACL *)ACL; - -/*! - @abstract Creates an ACL where only the provided user has access. - - @param user The user to assign access. - */ -+ (PFACL *)ACLWithUser:(PFUser *)user; - -///-------------------------------------- -/// @name Controlling Public Access -///-------------------------------------- - -/*! - @abstract Set whether the public is allowed to read this object. - - @param allowed Whether the public can read this object. - */ -- (void)setPublicReadAccess:(BOOL)allowed; - -/*! - @abstract Gets whether the public is allowed to read this object. - - @returns `YES` if the public read access is enabled, otherwise `NO`. - */ -- (BOOL)getPublicReadAccess; - -/*! - @abstract Set whether the public is allowed to write this object. - - @param allowed Whether the public can write this object. - */ -- (void)setPublicWriteAccess:(BOOL)allowed; - -/*! - @abstract Gets whether the public is allowed to write this object. - - @returns `YES` if the public write access is enabled, otherwise `NO`. - */ -- (BOOL)getPublicWriteAccess; - -///-------------------------------------- -/// @name Controlling Access Per-User -///-------------------------------------- - -/*! - @abstract Set whether the given user id is allowed to read this object. - - @param allowed Whether the given user can write this object. - @param userId The <[PFObject objectId]> of the user to assign access. - */ -- (void)setReadAccess:(BOOL)allowed forUserId:(NSString *)userId; - -/*! - @abstract Gets whether the given user id is *explicitly* allowed to read this object. - Even if this returns `NO`, the user may still be able to access it if returns `YES` - or if the user belongs to a role that has access. - - @param userId The <[PFObject objectId]> of the user for which to retrive access. - - @returns `YES` if the user with this `objectId` has *explicit* read access, otherwise `NO`. - */ -- (BOOL)getReadAccessForUserId:(NSString *)userId; - -/*! - @abstract Set whether the given user id is allowed to write this object. - - @param allowed Whether the given user can read this object. - @param userId The `objectId` of the user to assign access. - */ -- (void)setWriteAccess:(BOOL)allowed forUserId:(NSString *)userId; - -/*! - @abstract Gets whether the given user id is *explicitly* allowed to write this object. - Even if this returns NO, the user may still be able to write it if returns `YES` - or if the user belongs to a role that has access. - - @param userId The <[PFObject objectId]> of the user for which to retrive access. - - @returns `YES` if the user with this `objectId` has *explicit* write access, otherwise `NO`. - */ -- (BOOL)getWriteAccessForUserId:(NSString *)userId; - -/*! - @abstract Set whether the given user is allowed to read this object. - - @param allowed Whether the given user can read this object. - @param user The user to assign access. - */ -- (void)setReadAccess:(BOOL)allowed forUser:(PFUser *)user; - -/*! - @abstract Gets whether the given user is *explicitly* allowed to read this object. - Even if this returns `NO`, the user may still be able to access it if returns `YES` - or if the user belongs to a role that has access. - - @param user The user for which to retrive access. - - @returns `YES` if the user has *explicit* read access, otherwise `NO`. - */ -- (BOOL)getReadAccessForUser:(PFUser *)user; - -/*! - @abstract Set whether the given user is allowed to write this object. - - @param allowed Whether the given user can write this object. - @param user The user to assign access. - */ -- (void)setWriteAccess:(BOOL)allowed forUser:(PFUser *)user; - -/*! - @abstract Gets whether the given user is *explicitly* allowed to write this object. - Even if this returns `NO`, the user may still be able to write it if returns `YES` - or if the user belongs to a role that has access. - - @param user The user for which to retrive access. - - @returns `YES` if the user has *explicit* write access, otherwise `NO`. - */ -- (BOOL)getWriteAccessForUser:(PFUser *)user; - -///-------------------------------------- -/// @name Controlling Access Per-Role -///-------------------------------------- - -/*! - @abstract Get whether users belonging to the role with the given name are allowed to read this object. - Even if this returns `NO`, the role may still be able to read it if a parent role has read access. - - @param name The name of the role. - - @returns `YES` if the role has read access, otherwise `NO`. - */ -- (BOOL)getReadAccessForRoleWithName:(NSString *)name; - -/*! - @abstract Set whether users belonging to the role with the given name are allowed to read this object. - - @param allowed Whether the given role can read this object. - @param name The name of the role. - */ -- (void)setReadAccess:(BOOL)allowed forRoleWithName:(NSString *)name; - -/*! - @abstract Get whether users belonging to the role with the given name are allowed to write this object. - Even if this returns `NO`, the role may still be able to write it if a parent role has write access. - - @param name The name of the role. - - @returns `YES` if the role has read access, otherwise `NO`. - */ -- (BOOL)getWriteAccessForRoleWithName:(NSString *)name; - -/*! - @abstract Set whether users belonging to the role with the given name are allowed to write this object. - - @param allowed Whether the given role can write this object. - @param name The name of the role. - */ -- (void)setWriteAccess:(BOOL)allowed forRoleWithName:(NSString *)name; - -/*! - @abstract Get whether users belonging to the given role are allowed to read this object. - Even if this returns `NO`, the role may still be able to read it if a parent role has read access. - - @discussion The role must already be saved on the server and - it's data must have been fetched in order to use this method. - - @param role The name of the role. - - @returns `YES` if the role has read access, otherwise `NO`. - */ -- (BOOL)getReadAccessForRole:(PFRole *)role; - -/*! - @abstract Set whether users belonging to the given role are allowed to read this object. - - @discussion The role must already be saved on the server and - it's data must have been fetched in order to use this method. - - @param allowed Whether the given role can read this object. - @param role The role to assign access. - */ -- (void)setReadAccess:(BOOL)allowed forRole:(PFRole *)role; - -/*! - @abstract Get whether users belonging to the given role are allowed to write this object. - Even if this returns `NO`, the role may still be able to write it if a parent role has write access. - - @discussion The role must already be saved on the server and - it's data must have been fetched in order to use this method. - - @param role The name of the role. - - @returns `YES` if the role has write access, otherwise `NO`. - */ -- (BOOL)getWriteAccessForRole:(PFRole *)role; - -/*! - @abstract Set whether users belonging to the given role are allowed to write this object. - - @discussion The role must already be saved on the server and - it's data must have been fetched in order to use this method. - - @param allowed Whether the given role can write this object. - @param role The role to assign access. - */ -- (void)setWriteAccess:(BOOL)allowed forRole:(PFRole *)role; - -///-------------------------------------- -/// @name Setting Access Defaults -///-------------------------------------- - -/*! - @abstract Sets a default ACL that will be applied to all instances of when they are created. - - @param acl The ACL to use as a template for all instance of created after this method has been called. - This value will be copied and used as a template for the creation of new ACLs, so changes to the - instance after this method has been called will not be reflected in new instance of . - @param currentUserAccess - If `YES`, the `PFACL` that is applied to newly-created instance of will - provide read and write access to the <[PFUser currentUser]> at the time of creation. - - If `NO`, the provided `acl` will be used without modification. - - If `acl` is `nil`, this value is ignored. - */ -+ (void)setDefaultACL:(PF_NULLABLE PFACL *)acl withAccessForCurrentUser:(BOOL)currentUserAccess; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFAnalytics.h b/Parse.framework/Headers/PFAnalytics.h deleted file mode 100644 index a883453..0000000 --- a/Parse.framework/Headers/PFAnalytics.h +++ /dev/null @@ -1,167 +0,0 @@ -// -// PFAnalytics.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class BFTask; - -/*! - `PFAnalytics` provides an interface to Parse's logging and analytics backend. - - Methods will return immediately and cache the request (+ timestamp) to be - handled "eventually." That is, the request will be sent immediately if possible - or the next time a network connection is available. - */ -@interface PFAnalytics : NSObject - -///-------------------------------------- -/// @name App-Open / Push Analytics -///-------------------------------------- - -/*! - @abstract Tracks this application being launched. If this happened as the result of the - user opening a push notification, this method sends along information to - correlate this open with that push. - - @discussion Pass in `nil` to track a standard "application opened" event. - - @param launchOptions The `NSDictionary` indicating the reason the application was - launched, if any. This value can be found as a parameter to various - `UIApplicationDelegate` methods, and can be empty or `nil`. - - @returns Returns the task encapsulating the work being done. - */ -+ (BFTask *)trackAppOpenedWithLaunchOptions:(PF_NULLABLE NSDictionary *)launchOptions; - -/*! - @abstract Tracks this application being launched. - If this happened as the result of the user opening a push notification, - this method sends along information to correlate this open with that push. - - @discussion Pass in `nil` to track a standard "application opened" event. - - @param launchOptions The dictionary indicating the reason the application was - launched, if any. This value can be found as a parameter to various - `UIApplicationDelegate` methods, and can be empty or `nil`. - @param block The block to execute on server response. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)` - */ -+ (void)trackAppOpenedWithLaunchOptionsInBackground:(PF_NULLABLE NSDictionary *)launchOptions - block:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract Tracks this application being launched. If this happened as the result of the - user opening a push notification, this method sends along information to - correlate this open with that push. - - @param userInfo The Remote Notification payload, if any. This value can be - found either under `UIApplicationLaunchOptionsRemoteNotificationKey` on `launchOptions`, - or as a parameter to `application:didReceiveRemoteNotification:`. - This can be empty or `nil`. - - @returns Returns the task encapsulating the work being done. - */ -+ (BFTask *)trackAppOpenedWithRemoteNotificationPayload:(PF_NULLABLE NSDictionary *)userInfo; - -/*! - @abstract Tracks this application being launched. If this happened as the result of the - user opening a push notification, this method sends along information to - correlate this open with that push. - - @param userInfo The Remote Notification payload, if any. This value can be - found either under `UIApplicationLaunchOptionsRemoteNotificationKey` on `launchOptions`, - or as a parameter to `application:didReceiveRemoteNotification:`. This can be empty or `nil`. - @param block The block to execute on server response. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)` - */ -+ (void)trackAppOpenedWithRemoteNotificationPayloadInBackground:(PF_NULLABLE NSDictionary *)userInfo - block:(PF_NULLABLE PFBooleanResultBlock)block; - -///-------------------------------------- -/// @name Custom Analytics -///-------------------------------------- - -/*! - @abstract Tracks the occurrence of a custom event. - - @discussion Parse will store a data point at the time of invocation with the given event name. - - @param name The name of the custom event to report to Parse as having happened. - - @returns Returns the task encapsulating the work being done. - */ -+ (BFTask *)trackEvent:(NSString *)name; - -/*! - @abstract Tracks the occurrence of a custom event. Parse will store a data point at the - time of invocation with the given event name. The event will be sent at some - unspecified time in the future, even if Parse is currently inaccessible. - - @param name The name of the custom event to report to Parse as having happened. - @param block The block to execute on server response. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)` - */ -+ (void)trackEventInBackground:(NSString *)name block:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract Tracks the occurrence of a custom event with additional dimensions. Parse will - store a data point at the time of invocation with the given event name. - - @discussion Dimensions will allow segmentation of the occurrences of this custom event. - Keys and values should be NSStrings, and will throw otherwise. - - To track a user signup along with additional metadata, consider the following: - - NSDictionary *dimensions = @{ @"gender": @"m", - @"source": @"web", - @"dayType": @"weekend" }; - [PFAnalytics trackEvent:@"signup" dimensions:dimensions]; - - @warning There is a default limit of 8 dimensions per event tracked. - - @param name The name of the custom event to report to Parse as having happened. - @param dimensions The `NSDictionary` of information by which to segment this event. - - @returns Returns the task encapsulating the work being done. - */ -+ (BFTask *)trackEvent:(NSString *)name dimensions:(PF_NULLABLE NSDictionary *)dimensions; - -/*! - @abstract Tracks the occurrence of a custom event with additional dimensions. Parse will - store a data point at the time of invocation with the given event name. The - event will be sent at some unspecified time in the future, even if Parse is currently inaccessible. - - @discussionDimensions will allow segmentation of the occurrences of this custom event. - Keys and values should be NSStrings, and will throw otherwise. - - To track a user signup along with additional metadata, consider the following: - NSDictionary *dimensions = @{ @"gender": @"m", - @"source": @"web", - @"dayType": @"weekend" }; - [PFAnalytics trackEvent:@"signup" dimensions:dimensions]; - - There is a default limit of 8 dimensions per event tracked. - - @param name The name of the custom event to report to Parse as having happened. - @param dimensions The `NSDictionary` of information by which to segment this event. - @param block The block to execute on server response. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)` - */ -+ (void)trackEventInBackground:(NSString *)name - dimensions:(PF_NULLABLE NSDictionary *)dimensions - block:(PF_NULLABLE PFBooleanResultBlock)block; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFAnonymousUtils.h b/Parse.framework/Headers/PFAnonymousUtils.h deleted file mode 100644 index 7a55591..0000000 --- a/Parse.framework/Headers/PFAnonymousUtils.h +++ /dev/null @@ -1,82 +0,0 @@ -// -// PFAnonymousUtils.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#else -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -/*! - Provides utility functions for working with Anonymously logged-in users. - Anonymous users have some unique characteristics: - - - Anonymous users don't need a user name or password. - - Once logged out, an anonymous user cannot be recovered. - - When the current user is anonymous, the following methods can be used to switch - to a different user or convert the anonymous user into a regular one: - - signUp converts an anonymous user to a standard user with the given username and password. - Data associated with the anonymous user is retained. - - logIn switches users without converting the anonymous user. - Data associated with the anonymous user will be lost. - - Service logIn (e.g. Facebook, Twitter) will attempt to convert - the anonymous user into a standard user by linking it to the service. - If a user already exists that is linked to the service, it will instead switch to the existing user. - - Service linking (e.g. Facebook, Twitter) will convert the anonymous user - into a standard user by linking it to the service. - */ -@interface PFAnonymousUtils : NSObject - -///-------------------------------------- -/// @name Creating an Anonymous User -///-------------------------------------- - -/*! - @abstract Creates an anonymous user asynchronously and sets as a result to `BFTask`. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)logInInBackground; - -/*! - @abstract Creates an anonymous user. - - @param block The block to execute when anonymous user creation is complete. - It should have the following argument signature: `^(PFUser *user, NSError *error)`. - */ -+ (void)logInWithBlock:(PF_NULLABLE PFUserResultBlock)block; - -/* - @abstract Creates an anonymous user. - - @param target Target object for the selector. - @param selector The selector that will be called when the asynchronous request is complete. - It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError *)error`. - */ -+ (void)logInWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Determining Whether a User is Anonymous -///-------------------------------------- - -/*! - @abstract Whether the object is logged in anonymously. - - @param user object to check for anonymity. The user must be logged in on this device. - - @returns `YES` if the user is anonymous. `NO` if the user is not the current user or is not anonymous. - */ -+ (BOOL)isLinkedWithUser:(PF_NULLABLE PFUser *)user; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFCloud.h b/Parse.framework/Headers/PFCloud.h deleted file mode 100644 index f141ade..0000000 --- a/Parse.framework/Headers/PFCloud.h +++ /dev/null @@ -1,91 +0,0 @@ -// -// PFCloud.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class BFTask; - -/*! - The `PFCloud` class provides methods for interacting with Parse Cloud Functions. - */ -@interface PFCloud : NSObject - -/*! - @abstract Calls the given cloud function *synchronously* with the parameters provided. - - @param function The function name to call. - @param parameters The parameters to send to the function. - - @returns The response from the cloud function. - */ -+ (PF_NULLABLE_S id)callFunction:(NSString *)function withParameters:(PF_NULLABLE NSDictionary *)parameters; - -/*! - @abstract Calls the given cloud function *synchronously* with the parameters provided and - sets the error if there is one. - - @param function The function name to call. - @param parameters The parameters to send to the function. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns The response from the cloud function. - This result could be a `NSDictionary`, an `NSArray`, `NSNumber` or `NSString`. - */ -+ (PF_NULLABLE_S id)callFunction:(NSString *)function - withParameters:(PF_NULLABLE NSDictionary *)parameters - error:(NSError **)error; - -/*! - @abstract Calls the given cloud function *asynchronously* with the parameters provided. - - @param function The function name to call. - @param parameters The parameters to send to the function. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)callFunctionInBackground:(NSString *)function - withParameters:(PF_NULLABLE NSDictionary *)parameters; - -/*! - @abstract Calls the given cloud function *asynchronously* with the parameters provided - and executes the given block when it is done. - - @param function The function name to call. - @param parameters The parameters to send to the function. - @param block The block to execute when the function call finished. - It should have the following argument signature: `^(id result, NSError *error)`. - */ -+ (void)callFunctionInBackground:(NSString *)function - withParameters:(PF_NULLABLE NSDictionary *)parameters - block:(PF_NULLABLE PFIdResultBlock)block; - -/* - @abstract Calls the given cloud function *asynchronously* with the parameters provided - and then executes the given selector when it is done. - - @param function The function name to call. - @param parameters The parameters to send to the function. - @param target The object to call the selector on. - @param selector The selector to call when the function call finished. - It should have the following signature: `(void)callbackWithResult:(id)result error:(NSError *)error`. - Result will be `nil` if error is set and vice versa. - */ -+ (void)callFunctionInBackground:(NSString *)function - withParameters:(PF_NULLABLE NSDictionary *)parameters - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFConfig.h b/Parse.framework/Headers/PFConfig.h deleted file mode 100644 index 5eff195..0000000 --- a/Parse.framework/Headers/PFConfig.h +++ /dev/null @@ -1,105 +0,0 @@ -// -// PFConfig.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class BFTask; -@class PFConfig; - -typedef void(^PFConfigResultBlock)(PFConfig *PF_NULLABLE_S config, NSError *PF_NULLABLE_S error); - -/*! - `PFConfig` is a representation of the remote configuration object. - It enables you to add things like feature gating, a/b testing or simple "Message of the day". - */ -@interface PFConfig : NSObject - -///-------------------------------------- -/// @name Current Config -///-------------------------------------- - -/*! - @abstract Returns the most recently fetched config. - - @discussion If there was no config fetched - this method will return an empty instance of `PFConfig`. - - @returns Current, last fetched instance of PFConfig. - */ -+ (PFConfig *)currentConfig; - -///-------------------------------------- -/// @name Retrieving Config -///-------------------------------------- - -/*! - @abstract Gets the `PFConfig` object *synchronously* from the server. - - @returns Instance of `PFConfig` if the operation succeeded, otherwise `nil`. - */ -+ (PF_NULLABLE PFConfig *)getConfig; - -/*! - @abstract Gets the `PFConfig` object *synchronously* from the server and sets an error if it occurs. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Instance of PFConfig if the operation succeeded, otherwise `nil`. - */ -+ (PF_NULLABLE PFConfig *)getConfig:(NSError **)error; - -/*! - @abstract Gets the `PFConfig` *asynchronously* and sets it as a result of a task. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)getConfigInBackground; - -/*! - @abstract Gets the `PFConfig` *asynchronously* and executes the given callback block. - - @param block The block to execute. - It should have the following argument signature: `^(PFConfig *config, NSError *error)`. - */ -+ (void)getConfigInBackgroundWithBlock:(PF_NULLABLE PFConfigResultBlock)block; - -///-------------------------------------- -/// @name Parameters -///-------------------------------------- - -/*! - @abstract Returns the object associated with a given key. - - @param key The key for which to return the corresponding configuration value. - - @returns The value associated with `key`, or `nil` if there is no such value. - */ -- (PF_NULLABLE_S id)objectForKey:(NSString *)key; - -/*! - @abstract Returns the object associated with a given key. - - @discussion This method enables usage of literal syntax on `PFConfig`. - E.g. `NSString *value = config[@"key"];` - - @see objectForKey: - - @param keyedSubscript The keyed subscript for which to return the corresponding configuration value. - - @returns The value associated with `key`, or `nil` if there is no such value. - */ -- (PF_NULLABLE_S id)objectForKeyedSubscript:(NSString *)keyedSubscript; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFConstants.h b/Parse.framework/Headers/PFConstants.h deleted file mode 100644 index 884a76f..0000000 --- a/Parse.framework/Headers/PFConstants.h +++ /dev/null @@ -1,403 +0,0 @@ -// PFConstants.h -// Copyright 2011 Parse, Inc. All rights reserved. - -#import - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -@class PFObject; -@class PFUser; - -///-------------------------------------- -/// @name Version -///-------------------------------------- - -#define PARSE_VERSION @"1.7.1" - -extern NSInteger const PARSE_API_VERSION; - -///-------------------------------------- -/// @name Platform -///-------------------------------------- - -#define PARSE_IOS_ONLY (TARGET_OS_IPHONE) -#define PARSE_OSX_ONLY (TARGET_OS_MAC && !(TARGET_OS_IPHONE)) - -extern NSString *const PF_NONNULL_S kPFDeviceType; - -#if PARSE_IOS_ONLY -#import -#else -#import -@compatibility_alias UIImage NSImage; -@compatibility_alias UIColor NSColor; -@compatibility_alias UIView NSView; -#endif - -///-------------------------------------- -/// @name Server -///-------------------------------------- - -extern NSString *const PF_NONNULL_S kPFParseServer; - -///-------------------------------------- -/// @name Cache Policies -///-------------------------------------- - -/*! - `PFCachePolicy` specifies different caching policies that could be used with . - - This lets you show data when the user's device is offline, - or when the app has just started and network requests have not yet had time to complete. - Parse takes care of automatically flushing the cache when it takes up too much space. - - @warning Cache policy could only be set when Local Datastore is not enabled. - - @see PFQuery - */ -typedef NS_ENUM(uint8_t, PFCachePolicy) { - /*! - @abstract The query does not load from the cache or save results to the cache. - This is the default cache policy. - */ - kPFCachePolicyIgnoreCache = 0, - /*! - @abstract The query only loads from the cache, ignoring the network. - If there are no cached results, this causes a `NSError` with `kPFErrorCacheMiss` code. - */ - kPFCachePolicyCacheOnly, - /*! - @abstract The query does not load from the cache, but it will save results to the cache. - */ - kPFCachePolicyNetworkOnly, - /*! - @abstract The query first tries to load from the cache, but if that fails, it loads results from the network. - If there are no cached results, this causes a `NSError` with `kPFErrorCacheMiss` code. - */ - kPFCachePolicyCacheElseNetwork, - /*! - @abstract The query first tries to load from the network, but if that fails, it loads results from the cache. - If there are no cached results, this causes a `NSError` with `kPFErrorCacheMiss` code. - */ - kPFCachePolicyNetworkElseCache, - /*! - @abstract The query first loads from the cache, then loads from the network. - The callback will be called twice - first with the cached results, then with the network results. - Since it returns two results at different times, this cache policy cannot be used with synchronous or task methods. - */ - kPFCachePolicyCacheThenNetwork -}; - -///-------------------------------------- -/// @name Logging Levels -///-------------------------------------- - -/*! - `PFLogLevel` enum specifies different levels of logging that could be used to limit or display more messages in logs. - - @see [Parse setLogLevel:] - @see [Parse logLevel] - */ -typedef NS_ENUM(uint8_t, PFLogLevel) { - /*! - Log level that disables all logging. - */ - PFLogLevelNone = 0, - /*! - Log level that if set is going to output error messages to the log. - */ - PFLogLevelError = 1, - /*! - Log level that if set is going to output the following messages to log: - - Errors - - Warnings - */ - PFLogLevelWarning = 2, - /*! - Log level that if set is going to output the following messages to log: - - Errors - - Warnings - - Informational messages - */ - PFLogLevelInfo = 3, - /*! - Log level that if set is going to output the following messages to log: - - Errors - - Warnings - - Informational messages - - Debug messages - */ - PFLogLevelDebug = 4 -}; - -///-------------------------------------- -/// @name Errors -///-------------------------------------- - -extern NSString *const PF_NONNULL_S PFParseErrorDomain; - -/*! - `PFErrorCode` enum contains all custom error codes that are used as `code` for `NSError` for callbacks on all classes. - - These codes are used when `domain` of `NSError` that you receive is set to `PFParseErrorDomain`. - */ -typedef NS_ENUM(NSInteger, PFErrorCode) { - /*! - @abstract Internal server error. No information available. - */ - kPFErrorInternalServer = 1, - /*! - @abstract The connection to the Parse servers failed. - */ - kPFErrorConnectionFailed = 100, - /*! - @abstract Object doesn't exist, or has an incorrect password. - */ - kPFErrorObjectNotFound = 101, - /*! - @abstract You tried to find values matching a datatype that doesn't - support exact database matching, like an array or a dictionary. - */ - kPFErrorInvalidQuery = 102, - /*! - @abstract Missing or invalid classname. Classnames are case-sensitive. - They must start with a letter, and `a-zA-Z0-9_` are the only valid characters. - */ - kPFErrorInvalidClassName = 103, - /*! - @abstract Missing object id. - */ - kPFErrorMissingObjectId = 104, - /*! - @abstract Invalid key name. Keys are case-sensitive. - They must start with a letter, and `a-zA-Z0-9_` are the only valid characters. - */ - kPFErrorInvalidKeyName = 105, - /*! - @abstract Malformed pointer. Pointers must be arrays of a classname and an object id. - */ - kPFErrorInvalidPointer = 106, - /*! - @abstract Malformed json object. A json dictionary is expected. - */ - kPFErrorInvalidJSON = 107, - /*! - @abstract Tried to access a feature only available internally. - */ - kPFErrorCommandUnavailable = 108, - /*! - @abstract Field set to incorrect type. - */ - kPFErrorIncorrectType = 111, - /*! - @abstract Invalid channel name. A channel name is either an empty string (the broadcast channel) - or contains only `a-zA-Z0-9_` characters and starts with a letter. - */ - kPFErrorInvalidChannelName = 112, - /*! - @abstract Invalid device token. - */ - kPFErrorInvalidDeviceToken = 114, - /*! - @abstract Push is misconfigured. See details to find out how. - */ - kPFErrorPushMisconfigured = 115, - /*! - @abstract The object is too large. - */ - kPFErrorObjectTooLarge = 116, - /*! - @abstract That operation isn't allowed for clients. - */ - kPFErrorOperationForbidden = 119, - /*! - @abstract The results were not found in the cache. - */ - kPFErrorCacheMiss = 120, - /*! - @abstract Keys in `NSDictionary` values may not include `$` or `.`. - */ - kPFErrorInvalidNestedKey = 121, - /*! - @abstract Invalid file name. - A file name can contain only `a-zA-Z0-9_.` characters and should be between 1 and 36 characters. - */ - kPFErrorInvalidFileName = 122, - /*! - @abstract Invalid ACL. An ACL with an invalid format was saved. This should not happen if you use . - */ - kPFErrorInvalidACL = 123, - /*! - @abstract The request timed out on the server. Typically this indicates the request is too expensive. - */ - kPFErrorTimeout = 124, - /*! - @abstract The email address was invalid. - */ - kPFErrorInvalidEmailAddress = 125, - /*! - A unique field was given a value that is already taken. - */ - kPFErrorDuplicateValue = 137, - /*! - @abstract Role's name is invalid. - */ - kPFErrorInvalidRoleName = 139, - /*! - @abstract Exceeded an application quota. Upgrade to resolve. - */ - kPFErrorExceededQuota = 140, - /*! - @abstract Cloud Code script had an error. - */ - kPFScriptError = 141, - /*! - @abstract Cloud Code validation failed. - */ - kPFValidationError = 142, - /*! - @abstract Product purchase receipt is missing. - */ - kPFErrorReceiptMissing = 143, - /*! - @abstract Product purchase receipt is invalid. - */ - kPFErrorInvalidPurchaseReceipt = 144, - /*! - @abstract Payment is disabled on this device. - */ - kPFErrorPaymentDisabled = 145, - /*! - @abstract The product identifier is invalid. - */ - kPFErrorInvalidProductIdentifier = 146, - /*! - @abstract The product is not found in the App Store. - */ - kPFErrorProductNotFoundInAppStore = 147, - /*! - @abstract The Apple server response is not valid. - */ - kPFErrorInvalidServerResponse = 148, - /*! - @abstract Product fails to download due to file system error. - */ - kPFErrorProductDownloadFileSystemFailure = 149, - /*! - @abstract Fail to convert data to image. - */ - kPFErrorInvalidImageData = 150, - /*! - @abstract Unsaved file. - */ - kPFErrorUnsavedFile = 151, - /*! - @abstract Fail to delete file. - */ - kPFErrorFileDeleteFailure = 153, - /*! - @abstract Application has exceeded its request limit. - */ - kPFErrorRequestLimitExceeded = 155, - /*! - @abstract Invalid event name. - */ - kPFErrorInvalidEventName = 160, - /*! - @abstract Username is missing or empty. - */ - kPFErrorUsernameMissing = 200, - /*! - @abstract Password is missing or empty. - */ - kPFErrorUserPasswordMissing = 201, - /*! - @abstract Username has already been taken. - */ - kPFErrorUsernameTaken = 202, - /*! - @abstract Email has already been taken. - */ - kPFErrorUserEmailTaken = 203, - /*! - @abstract The email is missing, and must be specified. - */ - kPFErrorUserEmailMissing = 204, - /*! - @abstract A user with the specified email was not found. - */ - kPFErrorUserWithEmailNotFound = 205, - /*! - @abstract The user cannot be altered by a client without the session. - */ - kPFErrorUserCannotBeAlteredWithoutSession = 206, - /*! - @abstract Users can only be created through sign up. - */ - kPFErrorUserCanOnlyBeCreatedThroughSignUp = 207, - /*! - @abstract An existing Facebook account already linked to another user. - */ - kPFErrorFacebookAccountAlreadyLinked = 208, - /*! - @abstract An existing account already linked to another user. - */ - kPFErrorAccountAlreadyLinked = 208, - /*! - Error code indicating that the current session token is invalid. - */ - kPFErrorInvalidSessionToken = 209, - kPFErrorUserIdMismatch = 209, - /*! - @abstract Facebook id missing from request. - */ - kPFErrorFacebookIdMissing = 250, - /*! - @abstract Linked id missing from request. - */ - kPFErrorLinkedIdMissing = 250, - /*! - @abstract Invalid Facebook session. - */ - kPFErrorFacebookInvalidSession = 251, - /*! - @abstract Invalid linked session. - */ - kPFErrorInvalidLinkedSession = 251, -}; - -///-------------------------------------- -/// @name Blocks -///-------------------------------------- - -typedef void (^PFBooleanResultBlock)(BOOL succeeded, NSError *PF_NULLABLE_S error); -typedef void (^PFIntegerResultBlock)(int number, NSError *PF_NULLABLE_S error); -typedef void (^PFArrayResultBlock)(NSArray *PF_NULLABLE_S objects, NSError *PF_NULLABLE_S error); -typedef void (^PFObjectResultBlock)(PFObject *PF_NULLABLE_S object, NSError *PF_NULLABLE_S error); -typedef void (^PFSetResultBlock)(NSSet *PF_NULLABLE_S channels, NSError *PF_NULLABLE_S error); -typedef void (^PFUserResultBlock)(PFUser *PF_NULLABLE_S user, NSError *PF_NULLABLE_S error); -typedef void (^PFDataResultBlock)(NSData *PF_NULLABLE_S data, NSError *PF_NULLABLE_S error); -typedef void (^PFDataStreamResultBlock)(NSInputStream *PF_NULLABLE_S stream, NSError *PF_NULLABLE_S error); -typedef void (^PFStringResultBlock)(NSString *PF_NULLABLE_S string, NSError *PF_NULLABLE_S error); -typedef void (^PFIdResultBlock)(PF_NULLABLE_S id object, NSError *PF_NULLABLE_S error); -typedef void (^PFProgressBlock)(int percentDone); - -///-------------------------------------- -/// @name Deprecated Macros -///-------------------------------------- - -#ifndef PARSE_DEPRECATED -# ifdef __deprecated_msg -# define PARSE_DEPRECATED(_MSG) __deprecated_msg(_MSG) -# else -# ifdef __deprecated -# define PARSE_DEPRECATED(_MSG) __attribute__((deprecated)) -# else -# define PARSE_DEPRECATED(_MSG) -# endif -# endif -#endif diff --git a/Parse.framework/Headers/PFFile.h b/Parse.framework/Headers/PFFile.h deleted file mode 100644 index 213f255..0000000 --- a/Parse.framework/Headers/PFFile.h +++ /dev/null @@ -1,290 +0,0 @@ -// -// PFFile.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class BFTask; - -/*! - `PFFile` representes a file of binary data stored on the Parse servers. - This can be a image, video, or anything else that an application needs to reference in a non-relational way. - */ -@interface PFFile : NSObject - -///-------------------------------------- -/// @name Creating a PFFile -///-------------------------------------- - -/*! - @abstract Creates a file with given data. A name will be assigned to it by the server. - - @param data The contents of the new `PFFile`. - - @returns A new `PFFile`. - */ -+ (instancetype)fileWithData:(NSData *)data; - -/*! - @abstract Creates a file with given data and name. - - @param name The name of the new PFFile. The file name must begin with and - alphanumeric character, and consist of alphanumeric characters, periods, - spaces, underscores, or dashes. - @param data The contents of the new `PFFile`. - - @returns A new `PFFile` object. - */ -+ (instancetype)fileWithName:(PF_NULLABLE NSString *)name data:(NSData *)data; - -/*! - @abstract Creates a file with the contents of another file. - - @param name The name of the new `PFFile`. The file name must begin with and - alphanumeric character, and consist of alphanumeric characters, periods, - spaces, underscores, or dashes. - @param path The path to the file that will be uploaded to Parse. - */ -+ (instancetype)fileWithName:(PF_NULLABLE NSString *)name - contentsAtPath:(NSString *)path; - -/*! - @abstract Creates a file with given data, name and content type. - - @param name The name of the new `PFFile`. The file name must begin with and - alphanumeric character, and consist of alphanumeric characters, periods, - spaces, underscores, or dashes. - @param data The contents of the new `PFFile`. - @param contentType Represents MIME type of the data. - - @returns A new `PFFile` object. - */ -+ (instancetype)fileWithName:(PF_NULLABLE NSString *)name - data:(NSData *)data - contentType:(PF_NULLABLE NSString *)contentType; - -/*! - @abstract Creates a file with given data and content type. - - @param data The contents of the new `PFFile`. - @param contentType Represents MIME type of the data. - - @returns A new `PFFile` object. - */ -+ (instancetype)fileWithData:(NSData *)data contentType:(PF_NULLABLE NSString *)contentType; - -/*! - @abstract The name of the file. - - @discussion Before the file is saved, this is the filename given by - the user. After the file is saved, that name gets prefixed with a unique - identifier. - */ -@property (nonatomic, copy, readonly) NSString *name; - -/*! - @abstract The url of the file. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy, readonly) NSString *url; - -///-------------------------------------- -/// @name Storing Data with Parse -///-------------------------------------- - -/*! - @abstract Whether the file has been uploaded for the first time. - */ -@property (nonatomic, assign, readonly) BOOL isDirty; - -/*! - @abstract Saves the file *synchronously*. - - @returns Returns whether the save succeeded. - */ -- (BOOL)save; - -/*! - @abstract Saves the file *synchronously* and sets an error if it occurs. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the save succeeded. - */ -- (BOOL)save:(NSError **)error; - -/*! - @abstract Saves the file *asynchronously*. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)saveInBackground; - -/*! - @abstract Saves the file *asynchronously* and executes the given block. - - @param block The block should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -- (void)saveInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract Saves the file *asynchronously* and executes the given block. - - @discussion This method will execute the progressBlock periodically with the percent progress. - `progressBlock` will get called with `100` before `resultBlock` is called. - - @param block The block should have the following argument signature: `^(BOOL succeeded, NSError *error)` - @param progressBlock The block should have the following argument signature: `^(int percentDone)` - */ -- (void)saveInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block - progressBlock:(PF_NULLABLE PFProgressBlock)progressBlock; - -/* - @abstract Saves the file *asynchronously* and calls the given callback. - - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -- (void)saveInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Getting Data from Parse -///-------------------------------------- - -/*! - @abstract Whether the data is available in memory or needs to be downloaded. - */ -@property (assign, readonly) BOOL isDataAvailable; - -/*! - @abstract *Synchronously* gets the data from cache if available or fetches its contents from the network. - - @returns The `NSData` object containing file data. Returns `nil` if there was an error in fetching. - */ -- (PF_NULLABLE NSData *)getData; - -/*! - @abstract This method is like but avoids ever holding the entire `PFFile` contents in memory at once. - - @discussion This can help applications with many large files avoid memory warnings. - - @returns A stream containing the data. Returns `nil` if there was an error in fetching. - */ -- (PF_NULLABLE NSInputStream *)getDataStream; - -/*! - @abstract *Synchronously* gets the data from cache if available or fetches its contents from the network. - Sets an error if it occurs. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns The `NSData` object containing file data. Returns `nil` if there was an error in fetching. - */ -- (PF_NULLABLE NSData *)getData:(NSError **)error; - -/*! - @abstract This method is like but avoids ever holding the entire `PFFile` contents in memory at once. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns A stream containing the data. Returns nil if there was an error in - fetching. - */ -- (PF_NULLABLE NSInputStream *)getDataStream:(NSError **)error; - -/*! - @abstract This method is like but avoids ever holding the entire `PFFile` contents in memory at once. - - @discussion This can help applications with many large files avoid memory warnings. - - @see getData - - @returns A stream containing the data. Returns `nil` if there was an error in fetching. - */ -- (BFTask *)getDataInBackground; - -/*! - @abstract This method is like but avoids - ever holding the entire `PFFile` contents in memory at once. - - @discussion This can help applications with many large files avoid memory warnings. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)getDataStreamInBackground; - -/*! - @abstract *Asynchronously* gets the data from cache if available or fetches its contents from the network. - - @param block The block should have the following argument signature: `^(NSData *result, NSError *error)` - */ -- (void)getDataInBackgroundWithBlock:(PF_NULLABLE PFDataResultBlock)block; - -/*! - @abstract This method is like but avoids - ever holding the entire `PFFile` contents in memory at once. - - @discussion This can help applications with many large files avoid memory warnings. - - @param block The block should have the following argument signature: `(NSInputStream *result, NSError *error)` - */ -- (void)getDataStreamInBackgroundWithBlock:(PF_NULLABLE PFDataStreamResultBlock)block; - -/*! - @abstract *Asynchronously* gets the data from cache if available or fetches its contents from the network. - - @discussion This method will execute the progressBlock periodically with the percent progress. - `progressBlock` will get called with `100` before `resultBlock` is called. - - @param resultBlock The block should have the following argument signature: (NSData *result, NSError *error) - @param progressBlock The block should have the following argument signature: (int percentDone) - */ -- (void)getDataInBackgroundWithBlock:(PF_NULLABLE PFDataResultBlock)resultBlock - progressBlock:(PF_NULLABLE PFProgressBlock)progressBlock; - -/*! - @abstract This method is like but avoids - ever holding the entire `PFFile` contents in memory at once. - - @discussion This can help applications with many large files avoid memory warnings. - - @param resultBlock The block should have the following argument signature: `^(NSInputStream *result, NSError *error)`. - @param progressBlock The block should have the following argument signature: `^(int percentDone)`. - */ -- (void)getDataStreamInBackgroundWithBlock:(PF_NULLABLE PFDataStreamResultBlock)resultBlock - progressBlock:(PF_NULLABLE PFProgressBlock)progressBlock; - -/* - @abstract *Asynchronously* gets the data from cache if available or fetches its contents from the network. - - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSData *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - */ -- (void)getDataInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Interrupting a Transfer -///-------------------------------------- - -/*! - @abstract Cancels the current request (upload or download of file). - */ -- (void)cancel; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFGeoPoint.h b/Parse.framework/Headers/PFGeoPoint.h deleted file mode 100644 index ae2c80e..0000000 --- a/Parse.framework/Headers/PFGeoPoint.h +++ /dev/null @@ -1,115 +0,0 @@ -// -// PFGeoPoint.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import -#import - -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class PFGeoPoint; - -typedef void(^PFGeoPointResultBlock)(PFGeoPoint *PF_NULLABLE_S geoPoint, NSError *PF_NULLABLE_S error); - -/*! - `PFGeoPoint` may be used to embed a latitude / longitude point as the value for a key in a . - It could be used to perform queries in a geospatial manner using <[PFQuery whereKey:nearGeoPoint:]>. - - Currently, instances of may only have one key associated with a `PFGeoPoint` type. - */ -@interface PFGeoPoint : NSObject - -///-------------------------------------- -/// @name Creating a Geo Point -///-------------------------------------- - -/*! - @abstract Create a PFGeoPoint object. Latitude and longitude are set to `0.0`. - - @returns Returns a new `PFGeoPoint`. - */ -+ (PFGeoPoint *)geoPoint; - -/*! - @abstract Creates a new `PFGeoPoint` object for the given `CLLocation`, set to the location's coordinates. - - @param location Instace of `CLLocation`, with set latitude and longitude. - - @returns Returns a new PFGeoPoint at specified location. - */ -+ (PFGeoPoint *)geoPointWithLocation:(PF_NULLABLE CLLocation *)location; - -/*! - @abstract Create a new `PFGeoPoint` object with the specified latitude and longitude. - - @param latitude Latitude of point in degrees. - @param longitude Longitude of point in degrees. - - @returns New point object with specified latitude and longitude. - */ -+ (PFGeoPoint *)geoPointWithLatitude:(double)latitude longitude:(double)longitude; - -/*! - @abstract Fetches the current device location and executes a block with a new `PFGeoPoint` object. - - @param geoPointHandler A block which takes the newly created `PFGeoPoint` as an argument. - It should have the following argument signature: `^(PFGeoPoint *geoPoint, NSError *error)` - */ -+ (void)geoPointForCurrentLocationInBackground:(PF_NULLABLE PFGeoPointResultBlock)geoPointHandler; - -///-------------------------------------- -/// @name Controlling Position -///-------------------------------------- - -/*! - @abstract Latitude of point in degrees. Valid range is from `-90.0` to `90.0`. - */ -@property (nonatomic, assign) double latitude; - -/*! - @abstract Longitude of point in degrees. Valid range is from `-180.0` to `180.0`. - */ -@property (nonatomic, assign) double longitude; - -///-------------------------------------- -/// @name Calculating Distance -///-------------------------------------- - -/*! - @abstract Get distance in radians from this point to specified point. - - @param point `PFGeoPoint` that represents the location of other point. - - @returns Distance in radians between the receiver and `point`. - */ -- (double)distanceInRadiansTo:(PF_NULLABLE PFGeoPoint *)point; - -/*! - @abstract Get distance in miles from this point to specified point. - - @param point `PFGeoPoint` that represents the location of other point. - - @returns Distance in miles between the receiver and `point`. - */ -- (double)distanceInMilesTo:(PF_NULLABLE PFGeoPoint *)point; - -/*! - @abstract Get distance in kilometers from this point to specified point. - - @param point `PFGeoPoint` that represents the location of other point. - - @returns Distance in kilometers between the receiver and `point`. - */ -- (double)distanceInKilometersTo:(PF_NULLABLE PFGeoPoint *)point; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFInstallation.h b/Parse.framework/Headers/PFInstallation.h deleted file mode 100644 index 7e9bddf..0000000 --- a/Parse.framework/Headers/PFInstallation.h +++ /dev/null @@ -1,113 +0,0 @@ -// -// PFInstallation.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#import -#import -#import - -PF_ASSUME_NONNULL_BEGIN - -/*! - A Parse Framework Installation Object that is a local representation of an - installation persisted to the Parse cloud. This class is a subclass of a - , and retains the same functionality of a PFObject, but also extends - it with installation-specific fields and related immutability and validity - checks. - - A valid `PFInstallation` can only be instantiated via - <[PFInstallation currentInstallation]> because the required identifier fields - are readonly. The and fields are also readonly properties which - are automatically updated to match the device's time zone and application badge - when the `PFInstallation` is saved, thus these fields might not reflect the - latest device state if the installation has not recently been saved. - - `PFInstallation` objects which have a valid and are saved to - the Parse cloud can be used to target push notifications. - - This class is currently for iOS only. There is no `PFInstallation` for Parse - applications running on OS X, because they cannot receive push notifications. - */ - -@interface PFInstallation : PFObject - -///-------------------------------------- -/// @name Accessing the Current Installation -///-------------------------------------- - -/*! - @abstract Gets the currently-running installation from disk and returns an instance of it. - - @discussion If this installation is not stored on disk, returns a `PFInstallation` - with and fields set to those of the - current installation. - - @result Returns a `PFInstallation` that represents the currently-running installation. - */ -+ (instancetype)currentInstallation; - -///-------------------------------------- -/// @name Installation Properties -///-------------------------------------- - -/*! - @abstract The device type for the `PFInstallation`. - */ -@property (nonatomic, strong, readonly) NSString *deviceType; - -/*! - @abstract The installationId for the `PFInstallation`. - */ -@property (nonatomic, strong, readonly) NSString *installationId; - -/*! - @abstract The device token for the `PFInstallation`. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *deviceToken; - -/*! - @abstract The badge for the `PFInstallation`. - */ -@property (nonatomic, assign) NSInteger badge; - -/*! - @abstract The name of the time zone for the `PFInstallation`. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong, readonly) NSString *timeZone; - -/*! - @abstract The channels for the `PFInstallation`. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSArray *channels; - -/*! - @abstract Sets the device token string property from an `NSData`-encoded token. - - @param deviceTokenData A token that identifies the device. - */ -- (void)setDeviceTokenFromData:(PF_NULLABLE NSData *)deviceTokenData; - -///-------------------------------------- -/// @name Querying for Installations -///-------------------------------------- - -/*! - @abstract Creates a for `PFInstallation` objects. - - @discussion Only the following types of queries are allowed for installations: - - - `[query getObjectWithId:]` - - `[query whereKey:@"installationId" equalTo:]` - - `[query whereKey:@"installationId" matchesKey: inQuery:]` - - You can add additional query conditions, but one of the above must appear as a top-level `AND` clause in the query. - */ -+ (PF_NULLABLE PFQuery *)query; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFNetworkActivityIndicatorManager.h b/Parse.framework/Headers/PFNetworkActivityIndicatorManager.h deleted file mode 100644 index 04ca6b6..0000000 --- a/Parse.framework/Headers/PFNetworkActivityIndicatorManager.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// PFNetworkActivityIndicatorManager.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#import - -PF_ASSUME_NONNULL_BEGIN - -/*! - `PFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. - When enabled, it will start managing the network activity indicator in the status bar, - according to the network operations that are performed by Parse SDK. - - The number of active requests is incremented or decremented like a stack or a semaphore, - the activity indicator will animate, as long as the number is greater than zero. - */ -@interface PFNetworkActivityIndicatorManager : NSObject - -/*! - A Boolean value indicating whether the manager is enabled. - If `YES` - the manager will start managing the status bar network activity indicator, - according to the network operations that are performed by Parse SDK. - The default value is `YES`. - */ -@property (nonatomic, assign, getter = isEnabled) BOOL enabled; - -/*! - A Boolean value indicating whether the network activity indicator is currently displayed in the status bar. - */ -@property (nonatomic, assign, readonly, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; - -/*! - The value that indicates current network activities count. - */ -@property (nonatomic, assign, readonly) NSUInteger networkActivityCount; - -/*! - @abstract Returns the shared network activity indicator manager object for the system. - - @returns The systemwide network activity indicator manager. - */ -+ (instancetype)sharedManager; - -/*! - @abstract Increments the number of active network requests. - - @discussion If this number was zero before incrementing, - this will start animating network activity indicator in the status bar. - */ -- (void)incrementActivityCount; - -/*! - @abstract Decrements the number of active network requests. - - @discussion If this number becomes zero after decrementing, - this will stop animating network activity indicator in the status bar. - */ -- (void)decrementActivityCount; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFNullability.h b/Parse.framework/Headers/PFNullability.h deleted file mode 100644 index 507a91e..0000000 --- a/Parse.framework/Headers/PFNullability.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// PFNullability.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#ifndef Parse_PFNullability_h -#define Parse_PFNullability_h - -///-------------------------------------- -/// @name Nullability Annotation Support -///-------------------------------------- - -#if __has_feature(nullability) -# define PF_NONNULL nonnull -# define PF_NONNULL_S __nonnull -# define PF_NULLABLE nullable -# define PF_NULLABLE_S __nullable -# define PF_NULLABLE_PROPERTY nullable, -#else -# define PF_NONNULL -# define PF_NONNULL_S -# define PF_NULLABLE -# define PF_NULLABLE_S -# define PF_NULLABLE_PROPERTY -#endif - -#if __has_feature(assume_nonnull) -# ifdef NS_ASSUME_NONNULL_BEGIN -# define PF_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN -# else -# define PF_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") -# endif -# ifdef NS_ASSUME_NONNULL_END -# define PF_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END -# else -# define PF_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") -# endif -#else -# define PF_ASSUME_NONNULL_BEGIN -# define PF_ASSUME_NONNULL_END -#endif - -#endif diff --git a/Parse.framework/Headers/PFObject+Subclass.h b/Parse.framework/Headers/PFObject+Subclass.h deleted file mode 100644 index d99184d..0000000 --- a/Parse.framework/Headers/PFObject+Subclass.h +++ /dev/null @@ -1,134 +0,0 @@ -// -// PFObject+Subclass.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#else -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class PFQuery; - -/*! - ### Subclassing Notes - - Developers can subclass `PFObject` for a more native object-oriented class structure. - Strongly-typed subclasses of `PFObject` must conform to the protocol - and must call before <[Parse setApplicationId:clientKey:]> is called. - After this it will be returned by and other `PFObject` factories. - - All methods in except for <[PFSubclassing parseClassName]> - are already implemented in the `PFObject+Subclass` category. - - Including `PFObject+Subclass.h` in your implementation file provides these implementations automatically. - - Subclasses support simpler initializers, query syntax, and dynamic synthesizers. - The following shows an example subclass: - - \@interface MYGame : PFObject - - // Accessing this property is the same as objectForKey:@"title" - @property (nonatomic, strong) NSString *title; - - + (NSString *)parseClassName; - - @end - - - @implementation MYGame - - @dynamic title; - - + (NSString *)parseClassName { - return @"Game"; - } - - @end - - - MYGame *game = [[MYGame alloc] init]; - game.title = @"Bughouse"; - [game saveInBackground]; - */ -@interface PFObject (Subclass) - -///-------------------------------------- -/// @name Methods for Subclasses -///-------------------------------------- - -/*! - @abstract Designated initializer for subclasses. - This method can only be called on subclasses which conform to . - This method should not be overridden. - */ -- (instancetype)init; - -/*! - @abstract Creates an instance of the registered subclass with this class's . - - @discussion This helps a subclass ensure that it can be subclassed itself. - For example, `[PFUser object]` will return a `MyUser` object if `MyUser` is a registered subclass of `PFUser`. - For this reason, `[MyClass object]` is preferred to `[[MyClass alloc] init]`. - This method can only be called on subclasses which conform to `PFSubclassing`. - A default implementation is provided by `PFObject` which should always be sufficient. - */ -+ (instancetype)object; - -/*! - @abstract Creates a reference to an existing `PFObject` for use in creating associations between `PFObjects`. - - @discussion Calling on this object will return `NO` until or has been called. - This method can only be called on subclasses which conform to . - A default implementation is provided by `PFObject` which should always be sufficient. - No network request will be made. - - @param objectId The object id for the referenced object. - - @returns An instance of `PFObject` without data. - */ -+ (instancetype)objectWithoutDataWithObjectId:(PF_NULLABLE NSString *)objectId; - -/*! - @abstract Registers an Objective-C class for Parse to use for representing a given Parse class. - - @discussion Once this is called on a `PFObject` subclass, any `PFObject` Parse creates with a class name - that matches `[self parseClassName]` will be an instance of subclass. - This method can only be called on subclasses which conform to . - A default implementation is provided by `PFObject` which should always be sufficient. - */ -+ (void)registerSubclass; - -/*! - @abstract Returns a query for objects of type . - - @discussion This method can only be called on subclasses which conform to . - A default implementation is provided by which should always be sufficient. - */ -+ (PF_NULLABLE PFQuery *)query; - -/*! - @abstract Returns a query for objects of type with a given predicate. - - @discussion A default implementation is provided by which should always be sufficient. - @warning This method can only be called on subclasses which conform to . - - @param predicate The predicate to create conditions from. - - @returns An instance of . - - @see [PFQuery queryWithClassName:predicate:] - */ -+ (PF_NULLABLE PFQuery *)queryWithPredicate:(PF_NULLABLE NSPredicate *)predicate; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFObject.h b/Parse.framework/Headers/PFObject.h deleted file mode 100644 index ab45c91..0000000 --- a/Parse.framework/Headers/PFObject.h +++ /dev/null @@ -1,1423 +0,0 @@ -// -// PFObject.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#else -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@protocol PFSubclassing; -@class BFTask; -@class PFRelation; - -/*! - The name of the default pin that for PFObject local data store. - */ -extern NSString *const PFObjectDefaultPin; - -/*! - The `PFObject` class is a local representation of data persisted to the Parse cloud. - This is the main class that is used to interact with objects in your app. - */ -NS_REQUIRES_PROPERTY_DEFINITIONS -@interface PFObject : NSObject { - BOOL dirty; - - // An array of NSDictionary of NSString -> PFFieldOperation. - // Each dictionary has a subset of the object's keys as keys, and the - // changes to the value for that key as its value. - // There is always at least one dictionary of pending operations. - // Every time a save is started, a new dictionary is added to the end. - // Whenever a save completes, the new data is put into fetchedData, and - // a dictionary is removed from the start. - NSMutableArray *PF_NULLABLE_S operationSetQueue; - - // Our best estimate as to what the current data is, based on - // the last fetch from the server, and the set of pending operations. - NSMutableDictionary *PF_NULLABLE_S estimatedData; -} - -///-------------------------------------- -/// @name Creating a PFObject -///-------------------------------------- - -/*! - @abstract Creates a new PFObject with a class name. - - @param className A class name can be any alphanumeric string that begins with a letter. - It represents an object in your app, like a 'User' or a 'Document'. - - @returns Returns the object that is instantiated with the given class name. - */ -+ (instancetype)objectWithClassName:(NSString *)className; - -/*! - @abstract Creates a reference to an existing PFObject for use in creating associations between PFObjects. - - @discussion Calling on this object will return `NO` until has been called. - No network request will be made. - - @param className The object's class. - @param objectId The object id for the referenced object. - - @returns A `PFObject` instance without data. - */ -+ (instancetype)objectWithoutDataWithClassName:(NSString *)className - objectId:(PF_NULLABLE NSString *)objectId; - -/*! - @abstract Creates a new `PFObject` with a class name, initialized with data - constructed from the specified set of objects and keys. - - @param className The object's class. - @param dictionary An `NSDictionary` of keys and objects to set on the new `PFObject`. - - @returns A PFObject with the given class name and set with the given data. - */ -+ (PFObject *)objectWithClassName:(NSString *)className - dictionary:(PF_NULLABLE NSDictionary *)dictionary; - -/*! - @abstract Initializes a new empty `PFObject` instance with a class name. - - @param newClassName A class name can be any alphanumeric string that begins with a letter. - It represents an object in your app, like a 'User' or a 'Document'. - - @returns Returns the object that is instantiated with the given class name. - */ -- (instancetype)initWithClassName:(NSString *)newClassName; - -///-------------------------------------- -/// @name Managing Object Properties -///-------------------------------------- - -/*! - @abstract The class name of the object. - */ -@property (strong, readonly) NSString *parseClassName; - -/*! - @abstract The id of the object. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *objectId; - -/*! - @abstract When the object was last updated. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong, readonly) NSDate *updatedAt; - -/*! - @abstract When the object was created. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong, readonly) NSDate *createdAt; - -/*! - @abstract The ACL for this object. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) PFACL *ACL; - -/*! - @abstract Returns an array of the keys contained in this object. - - @discussion This does not include `createdAt`, `updatedAt`, `authData`, or `objectId`. - It does include things like username and ACL. - */ -- (NSArray *)allKeys; - -///-------------------------------------- -/// @name Accessors -///-------------------------------------- - -/*! - @abstract Returns the value associated with a given key. - - @param key The key for which to return the corresponding value. - */ -- (PF_NULLABLE_S id)objectForKey:(NSString *)key; - -/*! - @abstract Sets the object associated with a given key. - - @param object The object for `key`. A strong reference to the object is maintaned by PFObject. - Raises an `NSInvalidArgumentException` if `object` is `nil`. - If you need to represent a `nil` value - use `NSNull`. - @param key The key for `object`. - Raises an `NSInvalidArgumentException` if `key` is `nil`. - - @see setObject:forKeyedSubscript: - */ -- (void)setObject:(id)object forKey:(NSString *)key; - -/*! - @abstract Unsets a key on the object. - - @param key The key. - */ -- (void)removeObjectForKey:(NSString *)key; - -/*! - @abstract Returns the value associated with a given key. - - @discussion This method enables usage of literal syntax on `PFObject`. - E.g. `NSString *value = object[@"key"];` - - @param key The key for which to return the corresponding value. - - @see objectForKey: - */ -- (PF_NULLABLE_S id)objectForKeyedSubscript:(NSString *)key; - -/*! - @abstract Returns the value associated with a given key. - - @discussion This method enables usage of literal syntax on `PFObject`. - E.g. `object[@"key"] = @"value";` - - @param object The object for `key`. A strong reference to the object is maintaned by PFObject. - Raises an `NSInvalidArgumentException` if `object` is `nil`. - If you need to represent a `nil` value - use `NSNull`. - @param key The key for `object`. - Raises an `NSInvalidArgumentException` if `key` is `nil`. - - @see setObject:forKey: - */ -- (void)setObject:(PF_NULLABLE_S id)object forKeyedSubscript:(NSString *)key; - -/*! - @abstract Returns the relation object associated with the given key. - - @param key The key that the relation is associated with. - */ -- (PFRelation *)relationForKey:(NSString *)key; - -/*! - @abstract Returns the relation object associated with the given key. - - @param key The key that the relation is associated with. - - @deprecated Please use `[PFObject relationForKey:]` instead. - */ -- (PFRelation *)relationforKey:(NSString *)key PARSE_DEPRECATED("Please use -relationForKey: instead."); - -///-------------------------------------- -/// @name Array Accessors -///-------------------------------------- - -/*! - @abstract Adds an object to the end of the array associated with a given key. - - @param object The object to add. - @param key The key. - */ -- (void)addObject:(id)object forKey:(NSString *)key; - -/*! - @abstract Adds the objects contained in another array to the end of the array associated with a given key. - - @param objects The array of objects to add. - @param key The key. - */ -- (void)addObjectsFromArray:(NSArray *)objects forKey:(NSString *)key; - -/*! - @abstract Adds an object to the array associated with a given key, only if it is not already present in the array. - - @discussion The position of the insert is not guaranteed. - - @param object The object to add. - @param key The key. - */ -- (void)addUniqueObject:(id)object forKey:(NSString *)key; - -/*! - @abstract Adds the objects contained in another array to the array associated with a given key, - only adding elements which are not already present in the array. - - @dicsussion The position of the insert is not guaranteed. - - @param objects The array of objects to add. - @param key The key. - */ -- (void)addUniqueObjectsFromArray:(NSArray *)objects forKey:(NSString *)key; - -/*! - @abstract Removes all occurrences of an object from the array associated with a given key. - - @param object The object to remove. - @param key The key. - */ -- (void)removeObject:(id)object forKey:(NSString *)key; - -/*! - @abstract Removes all occurrences of the objects contained in another array from the array associated with a given key. - - @param objects The array of objects to remove. - @param key The key. - */ -- (void)removeObjectsInArray:(NSArray *)objects forKey:(NSString *)key; - -///-------------------------------------- -/// @name Increment -///-------------------------------------- - -/*! - @abstract Increments the given key by `1`. - - @param key The key. - */ -- (void)incrementKey:(NSString *)key; - -/*! - @abstract Increments the given key by a number. - - @param key The key. - @param amount The amount to increment. - */ -- (void)incrementKey:(NSString *)key byAmount:(NSNumber *)amount; - -///-------------------------------------- -/// @name Saving Objects -///-------------------------------------- - -/*! - @abstract *Synchronously* saves the `PFObject`. - - @returns Returns whether the save succeeded. - */ -- (BOOL)save; - -/*! - @abstract *Synchronously* saves the `PFObject` and sets an error if it occurs. - - @param error Pointer to an NSError that will be set if necessary. - - @returns Returns whether the save succeeded. - */ -- (BOOL)save:(NSError **)error; - -/*! - @abstract Saves the `PFObject` *asynchronously*. - - @returns The task that encapsulates the work being done. - */ -- (BFTask *)saveInBackground; - -/*! - @abstract Saves the `PFObject` *asynchronously* and executes the given callback block. - - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -- (void)saveInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract Saves the `PFObject` asynchronously and calls the given callback. - - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -- (void)saveInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract Saves this object to the server at some unspecified time in the future, - even if Parse is currently inaccessible. - - @discussion Use this when you may not have a solid network connection, and don't need to know when the save completes. - If there is some problem with the object such that it can't be saved, it will be silently discarded. If the save - completes successfully while the object is still in memory, then callback will be called. - - Objects saved with this method will be stored locally in an on-disk cache until they can be delivered to Parse. - They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection is - available. Objects saved this way will persist even after the app is closed, in which case they will be sent the - next time the app is opened. If more than 10MB of data is waiting to be sent, subsequent calls to - will cause old saves to be silently discarded until the connection can be re-established, and the queued objects - can be saved. - - @returns The task that encapsulates the work being done. - */ -- (BFTask *)saveEventually; - -/*! - @abstract Saves this object to the server at some unspecified time in the future, - even if Parse is currently inaccessible. - - @discussion Use this when you may not have a solid network connection, and don't need to know when the save completes. - If there is some problem with the object such that it can't be saved, it will be silently discarded. If the save - completes successfully while the object is still in memory, then callback will be called. - - Objects saved with this method will be stored locally in an on-disk cache until they can be delivered to Parse. - They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection is - available. Objects saved this way will persist even after the app is closed, in which case they will be sent the - next time the app is opened. If more than 10MB of data is waiting to be sent, subsequent calls to - will cause old saves to be silently discarded until the connection can be re-established, and the queued objects - can be saved. - - @param callback The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -- (void)saveEventually:(PF_NULLABLE PFBooleanResultBlock)callback; - -///-------------------------------------- -/// @name Saving Many Objects -///-------------------------------------- - -/*! - @abstract Saves a collection of objects *synchronously all at once. - - @param objects The array of objects to save. - - @returns Returns whether the save succeeded. - */ -+ (BOOL)saveAll:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract Saves a collection of objects *synchronously* all at once and sets an error if necessary. - - @param objects The array of objects to save. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the save succeeded. - */ -+ (BOOL)saveAll:(PF_NULLABLE NSArray *)objects error:(NSError **)error; - -/*! - @abstract Saves a collection of objects all at once *asynchronously*. - - @param objects The array of objects to save. - - @returns The task that encapsulates the work being done. - */ -+ (BFTask *)saveAllInBackground:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract Saves a collection of objects all at once `asynchronously` and executes the block when done. - - @param objects The array of objects to save. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)saveAllInBackground:(PF_NULLABLE NSArray *)objects - block:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract Saves a collection of objects all at once *asynchronously* and calls a callback when done. - - @param objects The array of objects to save. - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)number error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -+ (void)saveAllInBackground:(PF_NULLABLE NSArray *)objects - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Deleting Many Objects -///-------------------------------------- - -/*! - @abstract *Synchronously* deletes a collection of objects all at once. - - @param objects The array of objects to delete. - - @returns Returns whether the delete succeeded. - */ -+ (BOOL)deleteAll:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract *Synchronously* deletes a collection of objects all at once and sets an error if necessary. - - @param objects The array of objects to delete. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the delete succeeded. - */ -+ (BOOL)deleteAll:(PF_NULLABLE NSArray *)objects error:(NSError **)error; - -/*! - @abstract Deletes a collection of objects all at once asynchronously. - @param objects The array of objects to delete. - @returns The task that encapsulates the work being done. - */ -+ (BFTask *)deleteAllInBackground:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract Deletes a collection of objects all at once *asynchronously* and executes the block when done. - - @param objects The array of objects to delete. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)deleteAllInBackground:(PF_NULLABLE NSArray *)objects - block:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract Deletes a collection of objects all at once *asynchronously* and calls a callback when done. - - @param objects The array of objects to delete. - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)number error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -+ (void)deleteAllInBackground:(PF_NULLABLE NSArray *)objects - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Getting an Object -///-------------------------------------- - -/*! - @abstract Gets whether the `PFObject` has been fetched. - - @returns `YES` if the PFObject is new or has been fetched or refreshed, otherwise `NO`. - */ -- (BOOL)isDataAvailable; - -#if PARSE_IOS_ONLY - -/*! - @abstract Refreshes the PFObject with the current data from the server. - - @deprecated Please use `-fetch` instead. - */ -- (void)refresh PARSE_DEPRECATED("Please use `-fetch` instead."); - -/*! - @abstract *Synchronously* refreshes the `PFObject` with the current data from the server and sets an error if it occurs. - - @param error Pointer to an `NSError` that will be set if necessary. - - @deprecated Please use `-fetch:` instead. - */ -- (void)refresh:(NSError **)error PARSE_DEPRECATED("Please use `-fetch:` instead."); - -/*! - @abstract *Asynchronously* refreshes the `PFObject` and executes the given callback block. - - @param block The block to execute. - The block should have the following argument signature: `^(PFObject *object, NSError *error)` - - @deprecated Please use `-fetchInBackgroundWithBlock:` instead. - */ -- (void)refreshInBackgroundWithBlock:(PF_NULLABLE PFObjectResultBlock)block PARSE_DEPRECATED("Please use `-fetchInBackgroundWithBlock:` instead."); - -/* - @abstract *Asynchronously* refreshes the `PFObject` and calls the given callback. - - @param target The target on which the selector will be called. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(PFObject *)refreshedObject error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `refreshedObject` will be the `PFObject` with the refreshed data. - - @deprecated Please use `fetchInBackgroundWithTarget:selector:` instead. - */ -- (void)refreshInBackgroundWithTarget:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector PARSE_DEPRECATED("Please use `fetchInBackgroundWithTarget:selector:` instead."); - -#endif - -/*! - @abstract *Synchronously* fetches the PFObject with the current data from the server. - */ -- (void)fetch; -/*! - @abstract *Synchronously* fetches the PFObject with the current data from the server and sets an error if it occurs. - - @param error Pointer to an `NSError` that will be set if necessary. - */ -- (void)fetch:(NSError **)error; - -/*! - @abstract *Synchronously* fetches the `PFObject` data from the server if is `NO`. - */ -- (PF_NULLABLE PFObject *)fetchIfNeeded; - -/*! - @abstract *Synchronously* fetches the `PFObject` data from the server if is `NO`. - - @param error Pointer to an `NSError` that will be set if necessary. - */ -- (PF_NULLABLE PFObject *)fetchIfNeeded:(NSError **)error; - -/*! - @abstract Fetches the `PFObject` *asynchronously* and sets it as a result for the task. - - @returns The task that encapsulates the work being done. - */ -- (BFTask *)fetchInBackground; - -/*! - @abstract Fetches the PFObject *asynchronously* and executes the given callback block. - - @param block The block to execute. - It should have the following argument signature: `^(PFObject *object, NSError *error)`. - */ -- (void)fetchInBackgroundWithBlock:(PF_NULLABLE PFObjectResultBlock)block; - -/* - @abstract Fetches the `PFObject *asynchronously* and calls the given callback. - - @param target The target on which the selector will be called. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(PFObject *)refreshedObject error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `refreshedObject` will be the `PFObject` with the refreshed data. - */ -- (void)fetchInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract Fetches the `PFObject` data *asynchronously* if isDataAvailable is `NO`, - then sets it as a result for the task. - - @returns The task that encapsulates the work being done. - */ -- (BFTask *)fetchIfNeededInBackground; - -/*! - @abstract Fetches the `PFObject` data *asynchronously* if is `NO`, then calls the callback block. - - @param block The block to execute. - It should have the following argument signature: `^(PFObject *object, NSError *error)`. - */ -- (void)fetchIfNeededInBackgroundWithBlock:(PF_NULLABLE PFObjectResultBlock)block; - -/* - @abstract Fetches the PFObject's data asynchronously if isDataAvailable is false, then calls the callback. - - @param target The target on which the selector will be called. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(PFObject *)fetchedObject error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `refreshedObject` will be the `PFObject` with the refreshed data. - */ -- (void)fetchIfNeededInBackgroundWithTarget:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Getting Many Objects -///-------------------------------------- - -/*! - @abstract *Synchronously* fetches all of the `PFObject` objects with the current data from the server. - - @param objects The list of objects to fetch. - */ -+ (void)fetchAll:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract *Synchronously* fetches all of the `PFObject` objects with the current data from the server - and sets an error if it occurs. - - @param objects The list of objects to fetch. - @param error Pointer to an `NSError` that will be set if necessary. - */ -+ (void)fetchAll:(PF_NULLABLE NSArray *)objects error:(NSError **)error; - -/*! - @abstract *Synchronously* fetches all of the `PFObject` objects with the current data from the server. - @param objects The list of objects to fetch. - */ -+ (void)fetchAllIfNeeded:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract *Synchronously* fetches all of the `PFObject` objects with the current data from the server - and sets an error if it occurs. - - @param objects The list of objects to fetch. - @param error Pointer to an `NSError` that will be set if necessary. - */ -+ (void)fetchAllIfNeeded:(PF_NULLABLE NSArray *)objects error:(NSError **)error; - -/*! - @abstract Fetches all of the `PFObject` objects with the current data from the server *asynchronously*. - - @param objects The list of objects to fetch. - - @returns The task that encapsulates the work being done. - */ -+ (BFTask *)fetchAllInBackground:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract Fetches all of the `PFObject` objects with the current data from the server *asynchronously* - and calls the given block. - - @param objects The list of objects to fetch. - @param block The block to execute. - It should have the following argument signature: `^(NSArray *objects, NSError *error)`. - */ -+ (void)fetchAllInBackground:(PF_NULLABLE NSArray *)objects - block:(PF_NULLABLE PFArrayResultBlock)block; - -/* - @abstract Fetches all of the `PFObject` objects with the current data from the server *asynchronously* - and calls the given callback. - - @param objects The list of objects to fetch. - @param target The target on which the selector will be called. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSArray *)fetchedObjects error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `fetchedObjects` will the array of `PFObject` objects that were fetched. - */ -+ (void)fetchAllInBackground:(PF_NULLABLE NSArray *)objects - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract Fetches all of the `PFObject` objects with the current data from the server *asynchronously*. - - @param objects The list of objects to fetch. - - @returns The task that encapsulates the work being done. - */ -+ (BFTask *)fetchAllIfNeededInBackground:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract Fetches all of the PFObjects with the current data from the server *asynchronously* - and calls the given block. - - @param objects The list of objects to fetch. - @param block The block to execute. - It should have the following argument signature: `^(NSArray *objects, NSError *error)`. - */ -+ (void)fetchAllIfNeededInBackground:(PF_NULLABLE NSArray *)objects - block:(PF_NULLABLE PFArrayResultBlock)block; - -/* - @abstract Fetches all of the PFObjects with the current data from the server *asynchronously* - and calls the given callback. - - @param objects The list of objects to fetch. - @param target The target on which the selector will be called. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSArray *)fetchedObjects error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `fetchedObjects` will the array of `PFObject` objects that were fetched. - */ -+ (void)fetchAllIfNeededInBackground:(PF_NULLABLE NSArray *)objects - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Fetching From Local Datastore -///-------------------------------------- - -/*! - @abstract *Synchronously* loads data from the local datastore into this object, - if it has not been fetched from the server already. - */ -- (void)fetchFromLocalDatastore; - -/*! - @abstract *Synchronously* loads data from the local datastore into this object, if it has not been fetched - from the server already. - - @discussion If the object is not stored in the local datastore, this `error` will be set to - return kPFErrorCacheMiss. - - @param error Pointer to an `NSError` that will be set if necessary. - */ -- (void)fetchFromLocalDatastore:(NSError **)error; - -/*! - @abstract *Asynchronously* loads data from the local datastore into this object, - if it has not been fetched from the server already. - - @returns The task that encapsulates the work being done. - */ -- (BFTask *)fetchFromLocalDatastoreInBackground; - -/*! - @abstract *Asynchronously* loads data from the local datastore into this object, - if it has not been fetched from the server already. - - @param block The block to execute. - It should have the following argument signature: `^(PFObject *object, NSError *error)`. - */ -- (void)fetchFromLocalDatastoreInBackgroundWithBlock:(PF_NULLABLE PFObjectResultBlock)block; - -///-------------------------------------- -/// @name Deleting an Object -///-------------------------------------- - -/*! - @abstract *Synchronously* deletes the `PFObject`. - - @returns Returns whether the delete succeeded. - */ -- (BOOL)delete; - -/*! - @abstract *Synchronously* deletes the `PFObject` and sets an error if it occurs. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the delete succeeded. - */ -- (BOOL)delete:(NSError **)error; - -/*! - @abstract Deletes the `PFObject` *asynchronously*. - - @returns The task that encapsulates the work being done. - */ -- (BFTask *)deleteInBackground; - -/*! - @abstract Deletes the `PFObject` *asynchronously* and executes the given callback block. - - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -- (void)deleteInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract Deletes the `PFObject` *asynchronously* and calls the given callback. - - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -- (void)deleteInBackgroundWithTarget:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract Deletes this object from the server at some unspecified time in the future, - even if Parse is currently inaccessible. - - @discussion Use this when you may not have a solid network connection, - and don't need to know when the delete completes. If there is some problem with the object - such that it can't be deleted, the request will be silently discarded. - - Delete instructions made with this method will be stored locally in an on-disk cache until they can be transmitted - to Parse. They will be sent immediately if possible. Otherwise, they will be sent the next time a network connection - is available. Delete requests will persist even after the app is closed, in which case they will be sent the - next time the app is opened. If more than 10MB of or commands are waiting - to be sent, subsequent calls to or will cause old requests to be silently discarded - until the connection can be re-established, and the queued requests can go through. - - @returns The task that encapsulates the work being done. - */ -- (BFTask *)deleteEventually; - -///-------------------------------------- -/// @name Dirtiness -///-------------------------------------- - -/*! - @abstract Gets whether any key-value pair in this object (or its children) - has been added/updated/removed and not saved yet. - - @returns Returns whether this object has been altered and not saved yet. - */ -- (BOOL)isDirty; - -/*! - @abstract Get whether a value associated with a key has been added/updated/removed and not saved yet. - - @param key The key to check for - - @returns Returns whether this key has been altered and not saved yet. - */ -- (BOOL)isDirtyForKey:(NSString *)key; - - -///-------------------------------------- -/// @name Pinning -///-------------------------------------- - -/*! - @abstract *Synchronously* stores the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @returns Returns whether the pin succeeded. - - @see unpin: - @see PFObjectDefaultPin - */ -- (BOOL)pin; - -/*! - @abstract *Synchronously* stores the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the pin succeeded. - - @see unpin: - @see PFObjectDefaultPin - */ -- (BOOL)pin:(NSError **)error; - -/*! - @abstract *Synchronously* stores the object and every object it points to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @param name The name of the pin. - - @returns Returns whether the pin succeeded. - - @see unpinWithName: - */ -- (BOOL)pinWithName:(NSString *)name; - -/*! - @abstract *Synchronously* stores the object and every object it points to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @param name The name of the pin. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the pin succeeded. - - @see unpinWithName: - */ -- (BOOL)pinWithName:(NSString *)name - error:(NSError **)error; - -/*! - @abstract *Asynchronously* stores the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @returns The task that encapsulates the work being done. - - @see unpinInBackground - @see PFObjectDefaultPin - */ -- (BFTask *)pinInBackground; - -/*! - @abstract *Asynchronously* stores the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see unpinInBackgroundWithBlock: - @see PFObjectDefaultPin - */ -- (void)pinInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract *Asynchronously* stores the object and every object it points to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @param name The name of the pin. - - @returns The task that encapsulates the work being done. - - @see unpinInBackgroundWithName: - */ -- (BFTask *)pinInBackgroundWithName:(NSString *)name; - -/*! - @abstract *Asynchronously* stores the object and every object it points to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - <[PFObject objectWithoutDataWithClassName:objectId:]> and then call on it. - - @param name The name of the pin. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see unpinInBackgroundWithName:block: - */ -- (void)pinInBackgroundWithName:(NSString *)name block:(PF_NULLABLE PFBooleanResultBlock)block; - -///-------------------------------------- -/// @name Pinning Many Objects -///-------------------------------------- - -/*! - @abstract *Synchronously* stores the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - - @returns Returns whether the pin succeeded. - - @see unpinAll: - @see PFObjectDefaultPin - */ -+ (BOOL)pinAll:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract *Synchronously* stores the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the pin succeeded. - - @see unpinAll:error: - @see PFObjectDefaultPin - */ -+ (BOOL)pinAll:(PF_NULLABLE NSArray *)objects error:(NSError **)error; - -/*! - @abstract *Synchronously* stores the objects and every object they point to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - @param name The name of the pin. - - @returns Returns whether the pin succeeded. - - @see unpinAll:withName: - */ -+ (BOOL)pinAll:(PF_NULLABLE NSArray *)objects withName:(NSString *)name; - -/*! - @abstract *Synchronously* stores the objects and every object they point to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - @param name The name of the pin. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the pin succeeded. - - @see unpinAll:withName:error: - */ -+ (BOOL)pinAll:(PF_NULLABLE NSArray *)objects - withName:(NSString *)name - error:(NSError **)error; - -/*! - @abstract *Asynchronously* stores the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - - @returns The task that encapsulates the work being done. - - @see unpinAllInBackground: - @see PFObjectDefaultPin - */ -+ (BFTask *)pinAllInBackground:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract *Asynchronously* stores the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see unpinAllInBackground:block: - @see PFObjectDefaultPin - */ -+ (void)pinAllInBackground:(PF_NULLABLE NSArray *)objects block:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract *Asynchronously* stores the objects and every object they point to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - @param name The name of the pin. - - @returns The task that encapsulates the work being done. - - @see unpinAllInBackground:withName: - */ -+ (BFTask *)pinAllInBackground:(PF_NULLABLE NSArray *)objects withName:(NSString *)name; - -/*! - @abstract *Asynchronously* stores the objects and every object they point to in the local datastore, recursively. - - @discussion If those other objects have not been fetched from Parse, they will not be stored. However, - if they have changed data, all the changes will be retained. To get the objects back later, you can - use a that uses <[PFQuery fromLocalDatastore]>, or you can create an unfetched pointer with - `[PFObject objectWithoutDataWithClassName:objectId:]` and then call `fetchFromLocalDatastore:` on it. - - @param objects The objects to be pinned. - @param name The name of the pin. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see unpinAllInBackground:withName:block: - */ -+ (void)pinAllInBackground:(PF_NULLABLE NSArray *)objects - withName:(NSString *)name - block:(PF_NULLABLE PFBooleanResultBlock)block; - -///-------------------------------------- -/// @name Unpinning -///-------------------------------------- - -/*! - @abstract *Synchronously* removes the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @returns Returns whether the unpin succeeded. - - @see pin: - @see PFObjectDefaultPin - */ -- (BOOL)unpin; - -/*! - @abstract *Synchronously* removes the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the unpin succeeded. - - @see pin: - @see PFObjectDefaultPin - */ -- (BOOL)unpin:(NSError **)error; - -/*! - @abstract *Synchronously* removes the object and every object it points to in the local datastore, recursively. - - @param name The name of the pin. - - @returns Returns whether the unpin succeeded. - - @see pinWithName: - */ -- (BOOL)unpinWithName:(NSString *)name; - -/*! - @abstract *Synchronously* removes the object and every object it points to in the local datastore, recursively. - - @param name The name of the pin. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the unpin succeeded. - - @see pinWithName:error: - */ -- (BOOL)unpinWithName:(NSString *)name - error:(NSError **)error; - -/*! - @abstract *Asynchronously* removes the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @returns The task that encapsulates the work being done. - - @see pinInBackground - @see PFObjectDefaultPin - */ -- (BFTask *)unpinInBackground; - -/*! - @abstract *Asynchronously* removes the object and every object it points to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see pinInBackgroundWithBlock: - @see PFObjectDefaultPin - */ -- (void)unpinInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract *Asynchronously* removes the object and every object it points to in the local datastore, recursively. - - @param name The name of the pin. - - @returns The task that encapsulates the work being done. - - @see pinInBackgroundWithName: - */ -- (BFTask *)unpinInBackgroundWithName:(NSString *)name; - -/*! - @abstract *Asynchronously* removes the object and every object it points to in the local datastore, recursively. - - @param name The name of the pin. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see pinInBackgroundWithName:block: - */ -- (void)unpinInBackgroundWithName:(NSString *)name block:(PF_NULLABLE PFBooleanResultBlock)block; - -///-------------------------------------- -/// @name Unpinning Many Objects -///-------------------------------------- - -/*! - @abstract *Synchronously* removes all objects in the local datastore - using a default pin name: `PFObjectDefaultPin`. - - @returns Returns whether the unpin succeeded. - - @see PFObjectDefaultPin - */ -+ (BOOL)unpinAllObjects; - -/*! - @abstract *Synchronously* removes all objects in the local datastore - using a default pin name: `PFObjectDefaultPin`. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the unpin succeeded. - - @see PFObjectDefaultPin - */ -+ (BOOL)unpinAllObjects:(NSError **)error; - -/*! - @abstract *Synchronously* removes all objects with the specified pin name. - - @param name The name of the pin. - - @returns Returns whether the unpin succeeded. - */ -+ (BOOL)unpinAllObjectsWithName:(NSString *)name; - -/*! - @abstract *Synchronously* removes all objects with the specified pin name. - - @param name The name of the pin. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the unpin succeeded. - */ -+ (BOOL)unpinAllObjectsWithName:(NSString *)name - error:(NSError **)error; - -/*! - @abstract *Asynchronously* removes all objects in the local datastore - using a default pin name: `PFObjectDefaultPin`. - - @returns The task that encapsulates the work being done. - - @see PFObjectDefaultPin - */ -+ (BFTask *)unpinAllObjectsInBackground; - -/*! - @abstract *Asynchronously* removes all objects in the local datastore - using a default pin name: `PFObjectDefaultPin`. - - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see PFObjectDefaultPin - */ -+ (void)unpinAllObjectsInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract *Asynchronously* removes all objects with the specified pin name. - - @param name The name of the pin. - - @returns The task that encapsulates the work being done. - */ -+ (BFTask *)unpinAllObjectsInBackgroundWithName:(NSString *)name; - -/*! - @abstract *Asynchronously* removes all objects with the specified pin name. - - @param name The name of the pin. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)unpinAllObjectsInBackgroundWithName:(NSString *)name block:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract *Synchronously* removes the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @param objects The objects. - - @returns Returns whether the unpin succeeded. - - @see pinAll: - @see PFObjectDefaultPin - */ -+ (BOOL)unpinAll:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract *Synchronously* removes the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @param objects The objects. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the unpin succeeded. - - @see pinAll:error: - @see PFObjectDefaultPin - */ -+ (BOOL)unpinAll:(PF_NULLABLE NSArray *)objects error:(NSError **)error; - -/*! - @abstract *Synchronously* removes the objects and every object they point to in the local datastore, recursively. - - @param objects The objects. - @param name The name of the pin. - - @returns Returns whether the unpin succeeded. - - @see pinAll:withName: - */ -+ (BOOL)unpinAll:(PF_NULLABLE NSArray *)objects withName:(NSString *)name; - -/*! - @abstract *Synchronously* removes the objects and every object they point to in the local datastore, recursively. - - @param objects The objects. - @param name The name of the pin. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the unpin succeeded. - - @see pinAll:withName:error: - */ -+ (BOOL)unpinAll:(PF_NULLABLE NSArray *)objects - withName:(NSString *)name - error:(NSError **)error; - -/*! - @abstract *Asynchronously* removes the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @param objects The objects. - - @returns The task that encapsulates the work being done. - - @see pinAllInBackground: - @see PFObjectDefaultPin - */ -+ (BFTask *)unpinAllInBackground:(PF_NULLABLE NSArray *)objects; - -/*! - @abstract *Asynchronously* removes the objects and every object they point to in the local datastore, recursively, - using a default pin name: `PFObjectDefaultPin`. - - @param objects The objects. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see pinAllInBackground:block: - @see PFObjectDefaultPin - */ -+ (void)unpinAllInBackground:(PF_NULLABLE NSArray *)objects block:(PF_NULLABLE PFBooleanResultBlock)block; - -/*! - @abstract *Asynchronously* removes the objects and every object they point to in the local datastore, recursively. - - @param objects The objects. - @param name The name of the pin. - - @returns The task that encapsulates the work being done. - - @see pinAllInBackground:withName: - */ -+ (BFTask *)unpinAllInBackground:(PF_NULLABLE NSArray *)objects withName:(NSString *)name; - -/*! - @abstract *Asynchronously* removes the objects and every object they point to in the local datastore, recursively. - - @param objects The objects. - @param name The name of the pin. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - - @see pinAllInBackground:withName:block: - */ -+ (void)unpinAllInBackground:(PF_NULLABLE NSArray *)objects - withName:(NSString *)name - block:(PF_NULLABLE PFBooleanResultBlock)block; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFProduct.h b/Parse.framework/Headers/PFProduct.h deleted file mode 100644 index 939f136..0000000 --- a/Parse.framework/Headers/PFProduct.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// PFProduct.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import -#import -#import -#import - -PF_ASSUME_NONNULL_BEGIN - -/*! - The `PFProduct` class represents an in-app purchase product on the Parse server. - By default, products can only be created via the Data Browser. Saving a `PFProduct` will result in error. - However, the products' metadata information can be queried and viewed. - - This class is currently for iOS only. - */ -@interface PFProduct : PFObject - -///-------------------------------------- -/// @name Product-specific Properties -///-------------------------------------- - -/*! - @abstract The product identifier of the product. - - @discussion This should match the product identifier in iTunes Connect exactly. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *productIdentifier; - -/*! - @abstract The icon of the product. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) PFFile *icon; - -/*! - @abstract The title of the product. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *title; - -/*! - @abstract The subtitle of the product. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *subtitle; - -/*! - @abstract The order in which the product information is displayed in . - - @discussion The product with a smaller order is displayed earlier in the . - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSNumber *order; - -/*! - @abstract The name of the associated download. - - @discussion If there is no downloadable asset, it should be `nil`. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong, readonly) NSString *downloadName; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFPurchase.h b/Parse.framework/Headers/PFPurchase.h deleted file mode 100644 index 3a8e661..0000000 --- a/Parse.framework/Headers/PFPurchase.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// PFPurchase.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import -#import - -#import - -@class PFProduct; - -/*! - `PFPurchase` provides a set of APIs for working with in-app purchases. - - This class is currently for iOS only. - */ -@interface PFPurchase : NSObject - -/*! - @abstract Add application logic block which is run when buying a product. - - @discussion This method should be called once for each product, and should be called before - calling . All invocations to should happen within - the same method, and on the main thread. It is recommended to place all invocations of this method - in `application:didFinishLaunchingWithOptions:`. - - @param productIdentifier the product identifier - @param block The block to be run when buying a product. - */ -+ (void)addObserverForProduct:(NSString *)productIdentifier - block:(void(^)(SKPaymentTransaction *transaction))block; - -/*! - @abstract *Asynchronously* initiates the purchase for the product. - - @param productIdentifier the product identifier - @param block the completion block. - */ -+ (void)buyProduct:(NSString *)productIdentifier block:(void(^)(NSError *error))block; - -/*! - @abstract *Asynchronously* download the purchased asset, which is stored on Parse's server. - - @discussion Parse verifies the receipt with Apple and delivers the content only if the receipt is valid. - - @param transaction the transaction, which contains the receipt. - @param completion the completion block. - */ -+ (void)downloadAssetForTransaction:(SKPaymentTransaction *)transaction - completion:(void(^)(NSString *filePath, NSError *error))completion; - -/*! - @abstract *Asynchronously* download the purchased asset, which is stored on Parse's server. - - @discussion Parse verifies the receipt with Apple and delivers the content only if the receipt is valid. - - @param transaction the transaction, which contains the receipt. - @param completion the completion block. - @param progress the progress block, which is called multiple times to reveal progress of the download. - */ -+ (void)downloadAssetForTransaction:(SKPaymentTransaction *)transaction - completion:(void(^)(NSString *filePath, NSError *error))completion - progress:(PFProgressBlock)progress; - -/*! - @abstract *Asynchronously* restore completed transactions for the current user. - - @discussion Only nonconsumable purchases are restored. If observers for the products have been added before - calling this method, invoking the method reruns the application logic associated with the purchase. - - @warning This method is only important to developers who want to preserve purchase states across - different installations of the same app. - */ -+ (void)restore; - -/* - @abstract Returns a content path of the asset of a product, if it was purchased and downloaded. - - @discussion To download and verify purchases use . - - @warning This method will return `nil`, if the purchase wasn't verified or if the asset was not downloaded. - */ -+ (NSString *)assetContentPathForProduct:(PFProduct *)product; - -@end diff --git a/Parse.framework/Headers/PFPush.h b/Parse.framework/Headers/PFPush.h deleted file mode 100644 index 961a806..0000000 --- a/Parse.framework/Headers/PFPush.h +++ /dev/null @@ -1,523 +0,0 @@ -// -// PFPush.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#import - -@class BFTask; -@class PFQuery; - -/*! - The `PFPush` class defines a push notification that can be sent from a client device. - - The preferred way of modifying or retrieving channel subscriptions is to use - the class, instead of the class methods in `PFPush`. - - This class is currently for iOS only. Parse does not handle Push Notifications - to Parse applications running on OS X. Push Notifications can be sent from OS X - applications via Cloud Code or the REST API to push-enabled devices (e.g. iOS or Android). - */ -@interface PFPush : NSObject - -///-------------------------------------- -/// @name Creating a Push Notification -///-------------------------------------- - -+ (PFPush *)push; - -///-------------------------------------- -/// @name Configuring a Push Notification -///-------------------------------------- - -/*! - @abstract Sets the channel on which this push notification will be sent. - - @param channel The channel to set for this push. - The channel name must start with a letter and contain only letters, numbers, dashes, and underscores. - */ -- (void)setChannel:(NSString *)channel; - -/*! - @abstract Sets the array of channels on which this push notification will be sent. - - @param channels The array of channels to set for this push. - Each channel name must start with a letter and contain only letters, numbers, dashes, and underscores. - */ -- (void)setChannels:(NSArray *)channels; - -/*! - @abstract Sets an installation query to which this push notification will be sent. - - @discussion The query should be created via <[PFInstallation query]> and should not specify a skip, limit, or order. - - @param query The installation query to set for this push. - */ -- (void)setQuery:(PFQuery *)query; - -/*! - @abstract Sets an alert message for this push notification. - - @warning This will overwrite any data specified in setData. - - @param message The message to send in this push. - */ -- (void)setMessage:(NSString *)message; - -/*! - @abstract Sets an arbitrary data payload for this push notification. - - @discussion See the guide for information about the dictionary structure. - - @warning This will overwrite any data specified in setMessage. - - @param data The data to send in this push. - */ -- (void)setData:(NSDictionary *)data; - -/*! - @abstract Sets whether this push will go to Android devices. - - @param pushToAndroid Whether this push will go to Android devices. - - @deprecated Please use a `[PFInstallation query]` with a constraint on deviceType instead. - */ -- (void)setPushToAndroid:(BOOL)pushToAndroid PARSE_DEPRECATED("Please use a [PFInstallation query] with a constraint on deviceType."); - -/*! - @abstract Sets whether this push will go to iOS devices. - - @param pushToIOS Whether this push will go to iOS devices. - - @deprecated Please use a `[PFInstallation query]` with a constraint on deviceType instead. - */ -- (void)setPushToIOS:(BOOL)pushToIOS PARSE_DEPRECATED("Please use a [PFInstallation query] with a constraint on deviceType."); - -/*! - @abstract Sets the expiration time for this notification. - - @discussion The notification will be sent to devices which are either online - at the time the notification is sent, or which come online before the expiration time is reached. - Because device clocks are not guaranteed to be accurate, - most applications should instead use . - - @see expireAfterTimeInterval: - - @param date The time at which the notification should expire. - */ -- (void)expireAtDate:(NSDate *)date; - -/*! - @abstract Sets the time interval after which this notification should expire. - - @discussion This notification will be sent to devices which are either online at - the time the notification is sent, or which come online within the given - time interval of the notification being received by Parse's server. - An interval which is less than or equal to zero indicates that the - message should only be sent to devices which are currently online. - - @param timeInterval The interval after which the notification should expire. - */ -- (void)expireAfterTimeInterval:(NSTimeInterval)timeInterval; - -/*! - @abstract Clears both expiration values, indicating that the notification should never expire. - */ -- (void)clearExpiration; - -///-------------------------------------- -/// @name Sending Push Notifications -///-------------------------------------- - -/*! - @abstract *Synchronously* send a push message to a channel. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param message The message to send. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the send succeeded. - */ -+ (BOOL)sendPushMessageToChannel:(NSString *)channel - withMessage:(NSString *)message - error:(NSError **)error; - -/*! - @abstract *Asynchronously* send a push message to a channel. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param message The message to send. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)sendPushMessageToChannelInBackground:(NSString *)channel - withMessage:(NSString *)message; - -/*! - @abstract *Asynchronously* sends a push message to a channel and calls the given block. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param message The message to send. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)` - */ -+ (void)sendPushMessageToChannelInBackground:(NSString *)channel - withMessage:(NSString *)message - block:(PFBooleanResultBlock)block; - -/* - @abstract *Asynchronously* send a push message to a channel. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param message The message to send. - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -+ (void)sendPushMessageToChannelInBackground:(NSString *)channel - withMessage:(NSString *)message - target:(id)target - selector:(SEL)selector; - -/*! - @abstract Send a push message to a query. - - @param query The query to send to. The query must be a query created with <[PFInstallation query]>. - @param message The message to send. - @param error Pointer to an NSError that will be set if necessary. - - @returns Returns whether the send succeeded. - */ -+ (BOOL)sendPushMessageToQuery:(PFQuery *)query - withMessage:(NSString *)message - error:(NSError **)error; - -/*! - @abstract *Asynchronously* send a push message to a query. - - @param query The query to send to. The query must be a query created with <[PFInstallation query]>. - @param message The message to send. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)sendPushMessageToQueryInBackground:(PFQuery *)query - withMessage:(NSString *)message; - -/*! - @abstract *Asynchronously* sends a push message to a query and calls the given block. - - @param query The query to send to. The query must be a PFInstallation query - created with [PFInstallation query]. - @param message The message to send. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)` - */ -+ (void)sendPushMessageToQueryInBackground:(PFQuery *)query - withMessage:(NSString *)message - block:(PFBooleanResultBlock)block; - -/*! - @abstract *Synchronously* send this push message. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the send succeeded. - */ -- (BOOL)sendPush:(NSError **)error; - -/*! - @abstract *Asynchronously* send this push message. - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)sendPushInBackground; - -/*! - @abstract *Asynchronously* send this push message and executes the given callback block. - - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -- (void)sendPushInBackgroundWithBlock:(PFBooleanResultBlock)block; - -/* - @abstract *Asynchronously* send this push message and calls the given callback. - - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -- (void)sendPushInBackgroundWithTarget:(id)target selector:(SEL)selector; - -/*! - @abstract *Synchronously* send a push message with arbitrary data to a channel. - - @discussion See the guide for information about the dictionary structure. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param data The data to send. - @param error Pointer to an NSError that will be set if necessary. - - @returns Returns whether the send succeeded. - */ -+ (BOOL)sendPushDataToChannel:(NSString *)channel - withData:(NSDictionary *)data - error:(NSError **)error; - -/*! - @abstract *Asynchronously* send a push message with arbitrary data to a channel. - - @discussion See the guide for information about the dictionary structure. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param data The data to send. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)sendPushDataToChannelInBackground:(NSString *)channel - withData:(NSDictionary *)data; - -/*! - @abstract Asynchronously sends a push message with arbitrary data to a channel and calls the given block. - - @discussion See the guide for information about the dictionary structure. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param data The data to send. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)sendPushDataToChannelInBackground:(NSString *)channel - withData:(NSDictionary *)data - block:(PFBooleanResultBlock)block; - -/* - @abstract *Asynchronously* send a push message with arbitrary data to a channel. - - @discussion See the guide for information about the dictionary structure. - - @param channel The channel to send to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param data The data to send. - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -+ (void)sendPushDataToChannelInBackground:(NSString *)channel - withData:(NSDictionary *)data - target:(id)target - selector:(SEL)selector; - -/*! - @abstract *Synchronously* send a push message with arbitrary data to a query. - - @discussion See the guide for information about the dictionary structure. - - @param query The query to send to. The query must be a query - created with <[PFInstallation query]>. - @param data The data to send. - @param error Pointer to an NSError that will be set if necessary. - - @returns Returns whether the send succeeded. - */ -+ (BOOL)sendPushDataToQuery:(PFQuery *)query - withData:(NSDictionary *)data - error:(NSError **)error; - -/*! - @abstract Asynchronously send a push message with arbitrary data to a query. - - @discussion See the guide for information about the dictionary structure. - - @param query The query to send to. The query must be a query - created with <[PFInstallation query]>. - @param data The data to send. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)sendPushDataToQueryInBackground:(PFQuery *)query - withData:(NSDictionary *)data; - -/*! - @abstract *Asynchronously* sends a push message with arbitrary data to a query and calls the given block. - - @discussion See the guide for information about the dictionary structure. - - @param query The query to send to. The query must be a query - created with <[PFInstallation query]>. - @param data The data to send. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)sendPushDataToQueryInBackground:(PFQuery *)query - withData:(NSDictionary *)data - block:(PFBooleanResultBlock)block; - -///-------------------------------------- -/// @name Handling Notifications -///-------------------------------------- - -/*! - @abstract A default handler for push notifications while the app is active that - could be used to mimic the behavior of iOS push notifications while the app is backgrounded or not running. - - @discussion Call this from `application:didReceiveRemoteNotification:`. - - @param userInfo The userInfo dictionary you get in `appplication:didReceiveRemoteNotification:`. - */ -+ (void)handlePush:(NSDictionary *)userInfo; - -///-------------------------------------- -/// @name Managing Channel Subscriptions -///-------------------------------------- - -/*! - @abstract Store the device token locally for push notifications. - - @discussion Usually called from you main app delegate's `didRegisterForRemoteNotificationsWithDeviceToken:`. - - @param deviceToken Either as an `NSData` straight from `application:didRegisterForRemoteNotificationsWithDeviceToken:` - or as an `NSString` if you converted it yourself. - */ -+ (void)storeDeviceToken:(id)deviceToken; - -/*! - @abstract *Synchronously* get all the channels that this device is subscribed to. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns an `NSSet` containing all the channel names this device is subscribed to. - */ -+ (NSSet *)getSubscribedChannels:(NSError **)error; - -/*! - @abstract *Asynchronously* get all the channels that this device is subscribed to. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)getSubscribedChannelsInBackground; - -/*! - @abstract *Asynchronously* get all the channels that this device is subscribed to. - @param block The block to execute. - It should have the following argument signature: `^(NSSet *channels, NSError *error)`. - */ -+ (void)getSubscribedChannelsInBackgroundWithBlock:(PFSetResultBlock)block; - -/* - @abstract *Asynchronously* get all the channels that this device is subscribed to. - - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSSet *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - */ -+ (void)getSubscribedChannelsInBackgroundWithTarget:(id)target - selector:(SEL)selector; - -/*! - @abstract *Synchrnously* subscribes the device to a channel of push notifications. - - @param channel The channel to subscribe to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the subscribe succeeded. - */ -+ (BOOL)subscribeToChannel:(NSString *)channel error:(NSError **)error; - -/*! - @abstract *Asynchronously* subscribes the device to a channel of push notifications. - - @param channel The channel to subscribe to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)subscribeToChannelInBackground:(NSString *)channel; - -/*! - @abstract *Asynchronously* subscribes the device to a channel of push notifications and calls the given block. - - @param channel The channel to subscribe to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)` - */ -+ (void)subscribeToChannelInBackground:(NSString *)channel - block:(PFBooleanResultBlock)block; - -/* - @abstract *Asynchronously* subscribes the device to a channel of push notifications and calls the given callback. - - @param channel The channel to subscribe to. The channel name must start with - a letter and contain only letters, numbers, dashes, and underscores. - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -+ (void)subscribeToChannelInBackground:(NSString *)channel - target:(id)target - selector:(SEL)selector; - -/*! - @abstract *Synchronously* unsubscribes the device to a channel of push notifications. - - @param channel The channel to unsubscribe from. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns whether the unsubscribe succeeded. - */ -+ (BOOL)unsubscribeFromChannel:(NSString *)channel error:(NSError **)error; - -/*! - @abstract *Asynchronously* unsubscribes the device from a channel of push notifications. - - @param channel The channel to unsubscribe from. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)unsubscribeFromChannelInBackground:(NSString *)channel; - -/*! - @abstract *Asynchronously* unsubscribes the device from a channel of push notifications and calls the given block. - - @param channel The channel to unsubscribe from. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)unsubscribeFromChannelInBackground:(NSString *)channel - block:(PFBooleanResultBlock)block; - -/* - @abstract *Asynchronously* unsubscribes the device from a channel of push notifications and calls the given callback. - - @param channel The channel to unsubscribe from. - @param target The object to call selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -+ (void)unsubscribeFromChannelInBackground:(NSString *)channel - target:(id)target - selector:(SEL)selector; - -@end diff --git a/Parse.framework/Headers/PFQuery.h b/Parse.framework/Headers/PFQuery.h deleted file mode 100644 index 45b73b1..0000000 --- a/Parse.framework/Headers/PFQuery.h +++ /dev/null @@ -1,885 +0,0 @@ -// -// PFQuery.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#import -#import -#else -#import -#import -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class BFTask; - -/*! - The `PFQuery` class defines a query that is used to query for s. - */ -@interface PFQuery : NSObject - -///-------------------------------------- -/// @name Creating a Query for a Class -///-------------------------------------- - -/*! - @abstract Returns a `PFQuery` for a given class. - - @param className The class to query on. - - @returns A `PFQuery` object. - */ -+ (instancetype)queryWithClassName:(NSString *)className; - -/*! - @abstract Creates a PFQuery with the constraints given by predicate. - - @discussion The following types of predicates are supported: - - - Simple comparisons such as `=`, `!=`, `<`, `>`, `<=`, `>=`, and `BETWEEN` with a key and a constant. - - Containment predicates, such as `x IN {1, 2, 3}`. - - Key-existence predicates, such as `x IN SELF`. - - BEGINSWITH expressions. - - Compound predicates with `AND`, `OR`, and `NOT`. - - SubQueries with `key IN %@`, subquery. - - The following types of predicates are NOT supported: - - - Aggregate operations, such as `ANY`, `SOME`, `ALL`, or `NONE`. - - Regular expressions, such as `LIKE`, `MATCHES`, `CONTAINS`, or `ENDSWITH`. - - Predicates comparing one key to another. - - Complex predicates with many ORed clauses. - - @param className The class to query on. - @param predicate The predicate to create conditions from. - */ -+ (instancetype)queryWithClassName:(NSString *)className predicate:(PF_NULLABLE NSPredicate *)predicate; - -/*! - Initializes the query with a class name. - @param newClassName The class name. - */ -- (instancetype)initWithClassName:(NSString *)newClassName; - -/*! - The class name to query for - */ -@property (nonatomic, strong) NSString *parseClassName; - -///-------------------------------------- -/// @name Adding Basic Constraints -///-------------------------------------- - -/*! - @abstract Make the query include PFObjects that have a reference stored at the provided key. - - @discussion This has an effect similar to a join. You can use dot notation to specify which fields in - the included object are also fetch. - - @param key The key to load child s for. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)includeKey:(NSString *)key; - -/*! - @abstract Make the query restrict the fields of the returned s to include only the provided keys. - - @discussion If this is called multiple times, then all of the keys specified in each of the calls will be included. - - @param keys The keys to include in the result. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)selectKeys:(NSArray *)keys; - -/*! - @abstract Add a constraint that requires a particular key exists. - - @param key The key that should exist. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKeyExists:(NSString *)key; - -/*! - @abstract Add a constraint that requires a key not exist. - - @param key The key that should not exist. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKeyDoesNotExist:(NSString *)key; - -/*! - @abstract Add a constraint to the query that requires a particular key's object to be equal to the provided object. - - @param key The key to be constrained. - @param object The object that must be equalled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key equalTo:(id)object; - -/*! - @abstract Add a constraint to the query that requires a particular key's object to be less than the provided object. - - @param key The key to be constrained. - @param object The object that provides an upper bound. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key lessThan:(id)object; - -/*! - @abstract Add a constraint to the query that requires a particular key's object - to be less than or equal to the provided object. - - @param key The key to be constrained. - @param object The object that must be equalled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key lessThanOrEqualTo:(id)object; - -/*! - @abstract Add a constraint to the query that requires a particular key's object - to be greater than the provided object. - - @param key The key to be constrained. - @param object The object that must be equalled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key greaterThan:(id)object; - -/*! - @abstract Add a constraint to the query that requires a particular key's - object to be greater than or equal to the provided object. - - @param key The key to be constrained. - @param object The object that must be equalled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key greaterThanOrEqualTo:(id)object; - -/*! - @abstract Add a constraint to the query that requires a particular key's object - to be not equal to the provided object. - - @param key The key to be constrained. - @param object The object that must not be equalled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key notEqualTo:(id)object; - -/*! - @abstract Add a constraint to the query that requires a particular key's object - to be contained in the provided array. - - @param key The key to be constrained. - @param array The possible values for the key's object. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key containedIn:(NSArray *)array; - -/*! - @abstract Add a constraint to the query that requires a particular key's object - not be contained in the provided array. - - @param key The key to be constrained. - @param array The list of values the key's object should not be. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key notContainedIn:(NSArray *)array; - -/*! - @abstract Add a constraint to the query that requires a particular key's array - contains every element of the provided array. - - @param key The key to be constrained. - @param array The array of values to search for. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key containsAllObjectsInArray:(NSArray *)array; - -///-------------------------------------- -/// @name Adding Location Constraints -///-------------------------------------- - -/*! - @abstract Add a constraint to the query that requires a particular key's coordinates (specified via ) - be near a reference point. - - @discussion Distance is calculated based on angular distance on a sphere. Results will be sorted by distance - from reference point. - - @param key The key to be constrained. - @param geopoint The reference point represented as a . - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key nearGeoPoint:(PFGeoPoint *)geopoint; - -/*! - @abstract Add a constraint to the query that requires a particular key's coordinates (specified via ) - be near a reference point and within the maximum distance specified (in miles). - - @discussion Distance is calculated based on a spherical coordinate system. - Results will be sorted by distance (nearest to farthest) from the reference point. - - @param key The key to be constrained. - @param geopoint The reference point represented as a . - @param maxDistance Maximum distance in miles. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key - nearGeoPoint:(PFGeoPoint *)geopoint - withinMiles:(double)maxDistance; - -/*! - @abstract Add a constraint to the query that requires a particular key's coordinates (specified via ) - be near a reference point and within the maximum distance specified (in kilometers). - - @discussion Distance is calculated based on a spherical coordinate system. - Results will be sorted by distance (nearest to farthest) from the reference point. - - @param key The key to be constrained. - @param geopoint The reference point represented as a . - @param maxDistance Maximum distance in kilometers. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key - nearGeoPoint:(PFGeoPoint *)geopoint - withinKilometers:(double)maxDistance; - -/*! - Add a constraint to the query that requires a particular key's coordinates (specified via ) be near - a reference point and within the maximum distance specified (in radians). Distance is calculated based on - angular distance on a sphere. Results will be sorted by distance (nearest to farthest) from the reference point. - - @param key The key to be constrained. - @param geopoint The reference point as a . - @param maxDistance Maximum distance in radians. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key - nearGeoPoint:(PFGeoPoint *)geopoint - withinRadians:(double)maxDistance; - -/*! - @abstract Add a constraint to the query that requires a particular key's coordinates (specified via ) be - contained within a given rectangular geographic bounding box. - - @param key The key to be constrained. - @param southwest The lower-left inclusive corner of the box. - @param northeast The upper-right inclusive corner of the box. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key withinGeoBoxFromSouthwest:(PFGeoPoint *)southwest toNortheast:(PFGeoPoint *)northeast; - -///-------------------------------------- -/// @name Adding String Constraints -///-------------------------------------- - -/*! - @abstract Add a regular expression constraint for finding string values that match the provided regular expression. - - @warning This may be slow for large datasets. - - @param key The key that the string to match is stored in. - @param regex The regular expression pattern to match. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key matchesRegex:(NSString *)regex; - -/*! - @abstract Add a regular expression constraint for finding string values that match the provided regular expression. - - @warning This may be slow for large datasets. - - @param key The key that the string to match is stored in. - @param regex The regular expression pattern to match. - @param modifiers Any of the following supported PCRE modifiers: - - `i` - Case insensitive search - - `m` - Search across multiple lines of input - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key - matchesRegex:(NSString *)regex - modifiers:(PF_NULLABLE NSString *)modifiers; - -/*! - @abstract Add a constraint for finding string values that contain a provided substring. - - @warning This will be slow for large datasets. - - @param key The key that the string to match is stored in. - @param substring The substring that the value must contain. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key containsString:(PF_NULLABLE NSString *)substring; - -/*! - @abstract Add a constraint for finding string values that start with a provided prefix. - - @discussion This will use smart indexing, so it will be fast for large datasets. - - @param key The key that the string to match is stored in. - @param prefix The substring that the value must start with. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key hasPrefix:(PF_NULLABLE NSString *)prefix; - -/*! - @abstract Add a constraint for finding string values that end with a provided suffix. - - @warning This will be slow for large datasets. - - @param key The key that the string to match is stored in. - @param suffix The substring that the value must end with. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key hasSuffix:(PF_NULLABLE NSString *)suffix; - -///-------------------------------------- -/// @name Adding Subqueries -///-------------------------------------- - -/*! - Returns a `PFQuery` that is the `or` of the passed in queries. - - @param queries The list of queries to or together. - - @returns An instance of `PFQuery` that is the `or` of the passed in queries. - */ -+ (instancetype)orQueryWithSubqueries:(NSArray *)queries; - -/*! - @abstract Adds a constraint that requires that a key's value matches a value in another key - in objects returned by a sub query. - - @param key The key that the value is stored. - @param otherKey The key in objects in the returned by the sub query whose value should match. - @param query The query to run. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key - matchesKey:(NSString *)otherKey - inQuery:(PFQuery *)query; - -/*! - @abstract Adds a constraint that requires that a key's value `NOT` match a value in another key - in objects returned by a sub query. - - @param key The key that the value is stored. - @param otherKey The key in objects in the returned by the sub query whose value should match. - @param query The query to run. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key - doesNotMatchKey:(NSString *)otherKey - inQuery:(PFQuery *)query; - -/*! - @abstract Add a constraint that requires that a key's value matches a `PFQuery` constraint. - - @warning This only works where the key's values are s or arrays of s. - - @param key The key that the value is stored in - @param query The query the value should match - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key matchesQuery:(PFQuery *)query; - -/*! - @abstract Add a constraint that requires that a key's value to not match a `PFQuery` constraint. - - @warning This only works where the key's values are s or arrays of s. - - @param key The key that the value is stored in - @param query The query the value should not match - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)whereKey:(NSString *)key doesNotMatchQuery:(PFQuery *)query; - -///-------------------------------------- -/// @name Sorting -///-------------------------------------- - -/*! - @abstract Sort the results in *ascending* order with the given key. - - @param key The key to order by. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)orderByAscending:(NSString *)key; - -/*! - @abstract Additionally sort in *ascending* order by the given key. - - @discussion The previous keys provided will precedence over this key. - - @param key The key to order by. - */ -- (instancetype)addAscendingOrder:(NSString *)key; - -/*! - @abstract Sort the results in *descending* order with the given key. - - @param key The key to order by. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)orderByDescending:(NSString *)key; - -/*! - @abstract Additionally sort in *descending* order by the given key. - - @discussion The previous keys provided will precedence over this key. - - @param key The key to order by. - */ -- (instancetype)addDescendingOrder:(NSString *)key; - -/*! - @abstract Sort the results using a given sort descriptor. - - @param sortDescriptor The `NSSortDescriptor` to use to sort the results of the query. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)orderBySortDescriptor:(NSSortDescriptor *)sortDescriptor; - -/*! - @abstract Sort the results using a given array of sort descriptors. - - @param sortDescriptors An array of `NSSortDescriptor` objects to use to sort the results of the query. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)orderBySortDescriptors:(PF_NULLABLE NSArray *)sortDescriptors; - -///-------------------------------------- -/// @name Getting Objects by ID -///-------------------------------------- - -/*! - @abstract Returns a with a given class and id. - - @param objectClass The class name for the object that is being requested. - @param objectId The id of the object that is being requested. - - @returns The if found. Returns `nil` if the object isn't found, or if there was an error. - */ -+ (PF_NULLABLE PFObject *)getObjectOfClass:(NSString *)objectClass objectId:(NSString *)objectId; - -/*! - @abstract Returns a with a given class and id and sets an error if necessary. - - @param objectClass The class name for the object that is being requested. - @param objectId The id of the object that is being requested. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns The if found. Returns `nil` if the object isn't found, or if there was an `error`. - */ -+ (PF_NULLABLE PFObject *)getObjectOfClass:(NSString *)objectClass - objectId:(NSString *)objectId - error:(NSError **)error; - -/*! - @abstract Returns a with the given id. - - @warning This method mutates the query. - It will reset limit to `1`, skip to `0` and remove all conditions, leaving only `objectId`. - - @param objectId The id of the object that is being requested. - - @returns The if found. Returns nil if the object isn't found, or if there was an error. - */ -- (PF_NULLABLE PFObject *)getObjectWithId:(NSString *)objectId; - -/*! - @abstract Returns a with the given id and sets an error if necessary. - - @warning This method mutates the query. - It will reset limit to `1`, skip to `0` and remove all conditions, leaving only `objectId`. - - @param objectId The id of the object that is being requested. - @param error Pointer to an `NSError` that will be set if necessary. - - @returns The if found. Returns nil if the object isn't found, or if there was an error. - */ -- (PF_NULLABLE PFObject *)getObjectWithId:(NSString *)objectId error:(NSError **)error; - -/*! - @abstract Gets a asynchronously and calls the given block with the result. - - @warning This method mutates the query. - It will reset limit to `1`, skip to `0` and remove all conditions, leaving only `objectId`. - - @param objectId The id of the object that is being requested. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)getObjectInBackgroundWithId:(NSString *)objectId; - -/*! - @abstract Gets a asynchronously and calls the given block with the result. - - @warning This method mutates the query. - It will reset limit to `1`, skip to `0` and remove all conditions, leaving only `objectId`. - - @param objectId The id of the object that is being requested. - @param block The block to execute. - The block should have the following argument signature: `^(NSArray *object, NSError *error)` - */ -- (void)getObjectInBackgroundWithId:(NSString *)objectId - block:(PF_NULLABLE PFObjectResultBlock)block; - -/* - @abstract Gets a asynchronously. - - This mutates the PFQuery. It will reset limit to `1`, skip to `0` and remove all conditions, leaving only `objectId`. - - @param objectId The id of the object being requested. - @param target The target for the callback selector. - @param selector The selector for the callback. - It should have the following signature: `(void)callbackWithResult:(id)result error:(NSError *)error`. - Result will be `nil` if error is set and vice versa. - */ -- (void)getObjectInBackgroundWithId:(NSString *)objectId - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Getting User Objects -///-------------------------------------- - -/*! - @abstract Returns a with a given id. - - @param objectId The id of the object that is being requested. - - @returns The PFUser if found. Returns nil if the object isn't found, or if there was an error. - */ -+ (PF_NULLABLE PFUser *)getUserObjectWithId:(NSString *)objectId; - -/*! - Returns a PFUser with a given class and id and sets an error if necessary. - @param objectId The id of the object that is being requested. - @param error Pointer to an NSError that will be set if necessary. - @result The PFUser if found. Returns nil if the object isn't found, or if there was an error. - */ -+ (PF_NULLABLE PFUser *)getUserObjectWithId:(NSString *)objectId - error:(NSError **)error; - -/*! - @deprecated Please use [PFUser query] instead. - */ -+ (instancetype)queryForUser PARSE_DEPRECATED("Use [PFUser query] instead."); - -///-------------------------------------- -/// @name Getting all Matches for a Query -///-------------------------------------- - -/*! - @abstract Finds objects *synchronously* based on the constructed query. - - @returns Returns an array of objects that were found. - */ -- (PF_NULLABLE NSArray *)findObjects; - -/*! - @abstract Finds objects *synchronously* based on the constructed query and sets an error if there was one. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns an array of objects that were found. - */ -- (PF_NULLABLE NSArray *)findObjects:(NSError **)error; - -/*! - @abstract Finds objects *asynchronously* and sets the `NSArray` of objects as a result of the task. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)findObjectsInBackground; - -/*! - @abstract Finds objects *asynchronously* and calls the given block with the results. - - @param block The block to execute. - It should have the following argument signature: `^(NSArray *objects, NSError *error)` - */ -- (void)findObjectsInBackgroundWithBlock:(PF_NULLABLE PFArrayResultBlock)block; - -/* - @abstract Finds objects *asynchronously* and calls the given callback with the results. - - @param target The object to call the selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(id)result error:(NSError *)error`. - Result will be `nil` if error is set and vice versa. - */ -- (void)findObjectsInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Getting the First Match in a Query -///-------------------------------------- - -/*! - @abstract Gets an object *synchronously* based on the constructed query. - - @warning This method mutates the query. It will reset the limit to `1`. - - @returns Returns a , or `nil` if none was found. - */ -- (PF_NULLABLE PFObject *)getFirstObject; - -/*! - @abstract Gets an object *synchronously* based on the constructed query and sets an error if any occurred. - - @warning This method mutates the query. It will reset the limit to `1`. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns a , or `nil` if none was found. - */ -- (PF_NULLABLE PFObject *)getFirstObject:(NSError **)error; - -/*! - @abstract Gets an object *asynchronously* and sets it as a result of the task. - - @warning This method mutates the query. It will reset the limit to `1`. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)getFirstObjectInBackground; - -/*! - @abstract Gets an object *asynchronously* and calls the given block with the result. - - @warning This method mutates the query. It will reset the limit to `1`. - - @param block The block to execute. - It should have the following argument signature: `^(PFObject *object, NSError *error)`. - `result` will be `nil` if `error` is set OR no object was found matching the query. - `error` will be `nil` if `result` is set OR if the query succeeded, but found no results. - */ -- (void)getFirstObjectInBackgroundWithBlock:(PF_NULLABLE PFObjectResultBlock)block; - -/* - @abstract Gets an object *asynchronously* and calls the given callback with the results. - - @warning This method mutates the query. It will reset the limit to `1`. - - @param target The object to call the selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(PFObject *)result error:(NSError *)error`. - `result` will be `nil` if `error` is set OR no object was found matching the query. - `error` will be `nil` if `result` is set OR if the query succeeded, but found no results. - */ -- (void)getFirstObjectInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Counting the Matches in a Query -///-------------------------------------- - -/*! - @abstract Counts objects *synchronously* based on the constructed query. - - @returns Returns the number of objects that match the query, or `-1` if there is an error. - */ -- (NSInteger)countObjects; - -/*! - @abstract Counts objects *synchronously* based on the constructed query and sets an error if there was one. - - @param error Pointer to an `NSError` that will be set if necessary. - - @returns Returns the number of objects that match the query, or `-1` if there is an error. - */ -- (NSInteger)countObjects:(NSError **)error; - -/*! - @abstract Counts objects *asynchronously* and sets `NSNumber` with count as a result of the task. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)countObjectsInBackground; - -/*! - @abstract Counts objects *asynchronously* and calls the given block with the counts. - - @param block The block to execute. - It should have the following argument signature: `^(int count, NSError *error)` - */ -- (void)countObjectsInBackgroundWithBlock:(PF_NULLABLE PFIntegerResultBlock)block; - -/* - @abstract Counts objects *asynchronously* and calls the given callback with the count. - - @param target The object to call the selector on. - @param selector The selector to call. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - */ -- (void)countObjectsInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Cancelling a Query -///-------------------------------------- - -/*! - @abstract Cancels the current network request (if any). Ensures that callbacks won't be called. - */ -- (void)cancel; - -///-------------------------------------- -/// @name Paginating Results -///-------------------------------------- - -/*! - @abstract A limit on the number of objects to return. The default limit is `100`, with a - maximum of 1000 results being returned at a time. - - @warning If you are calling `findObjects` with `limit = 1`, you may find it easier to use `getFirst` instead. - */ -@property (nonatomic, assign) NSInteger limit; - -/*! - @abstract The number of objects to skip before returning any. - */ -@property (nonatomic, assign) NSInteger skip; - -///-------------------------------------- -/// @name Controlling Caching Behavior -///-------------------------------------- - -/*! - @abstract The cache policy to use for requests. - - Not allowed when Pinning is enabled. - - @see fromLocalDatastore - @see fromPin - @see fromPinWithName: - */ -@property (assign, readwrite) PFCachePolicy cachePolicy; - -/* ! - @abstract The age after which a cached value will be ignored - */ -@property (assign, readwrite) NSTimeInterval maxCacheAge; - -/*! - @abstract Returns whether there is a cached result for this query. - - @result `YES` if there is a cached result for this query, otherwise `NO`. - */ -- (BOOL)hasCachedResult; - -/*! - @abstract Clears the cached result for this query. If there is no cached result, this is a noop. - */ -- (void)clearCachedResult; - -/*! - @abstract Clears the cached results for all queries. - */ -+ (void)clearAllCachedResults; - -///-------------------------------------- -/// @name Query Source -///-------------------------------------- - -/*! - @abstract Change the source of this query to all pinned objects. - - @warning Requires Local Datastore to be enabled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - - @see cachePolicy - */ -- (instancetype)fromLocalDatastore; - -/*! - @abstract Change the source of this query to the default group of pinned objects. - - @warning Requires Local Datastore to be enabled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - - @see PFObjectDefaultPin - @see cachePolicy - */ -- (instancetype)fromPin; - -/*! - @abstract Change the source of this query to a specific group of pinned objects. - - @warning Requires Local Datastore to be enabled. - - @param name The pinned group. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - - @see PFObjectDefaultPin - @see cachePolicy - */ -- (instancetype)fromPinWithName:(PF_NULLABLE NSString *)name; - -/*! - @abstract Ignore ACLs when querying from the Local Datastore. - - @discussion This is particularly useful when querying for objects with Role based ACLs set on them. - - @warning Requires Local Datastore to be enabled. - - @returns The same instance of `PFQuery` as the receiver. This allows method chaining. - */ -- (instancetype)ignoreACLs; - -///-------------------------------------- -/// @name Advanced Settings -///-------------------------------------- - -/*! - @abstract Whether or not performance tracing should be done on the query. - - @warning This should not be set to `YES` in most cases. - */ -@property (nonatomic, assign) BOOL trace; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFRelation.h b/Parse.framework/Headers/PFRelation.h deleted file mode 100644 index cbc182d..0000000 --- a/Parse.framework/Headers/PFRelation.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// PFRelation.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#import -#else -#import -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -/*! - The `PFRelation` class that is used to access all of the children of a many-to-many relationship. - Each instance of `PFRelation` is associated with a particular parent object and key. - */ -@interface PFRelation : NSObject - -/*! - @abstract The name of the class of the target child objects. - */ -@property (nonatomic, strong) NSString *targetClass; - -///-------------------------------------- -/// @name Accessing Objects -///-------------------------------------- - -/*! - @abstract Returns a object that can be used to get objects in this relation. - */ -- (PF_NULLABLE PFQuery *)query; - -///-------------------------------------- -/// @name Modifying Relations -///-------------------------------------- - -/*! - @abstract Adds a relation to the passed in object. - - @param object A object to add relation to. - */ -- (void)addObject:(PFObject *)object; - -/*! - @abstract Removes a relation to the passed in object. - - @param object A object to add relation to. - */ -- (void)removeObject:(PFObject *)object; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFRole.h b/Parse.framework/Headers/PFRole.h deleted file mode 100644 index effc7de..0000000 --- a/Parse.framework/Headers/PFRole.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// PFRole.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#import -#else -#import -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -/*! - The `PFRole` class represents a Role on the Parse server. - `PFRoles` represent groupings of objects for the purposes of granting permissions - (e.g. specifying a for a ). - Roles are specified by their sets of child users and child roles, - all of which are granted any permissions that the parent role has. - - Roles must have a name (which cannot be changed after creation of the role), and must specify an ACL. - */ -@interface PFRole : PFObject - -///-------------------------------------- -/// @name Creating a New Role -///-------------------------------------- - -/*! - @abstract Constructs a new `PFRole` with the given name. - If no default ACL has been specified, you must provide an ACL for the role. - - @param name The name of the Role to create. - */ -- (instancetype)initWithName:(NSString *)name; - -/*! - @abstract Constructs a new `PFRole` with the given name. - - @param name The name of the Role to create. - @param acl The ACL for this role. Roles must have an ACL. - */ -- (instancetype)initWithName:(NSString *)name acl:(PF_NULLABLE PFACL *)acl; - -/*! - @abstract Constructs a new `PFRole` with the given name. - - @discussion If no default ACL has been specified, you must provide an ACL for the role. - - @param name The name of the Role to create. - */ -+ (instancetype)roleWithName:(NSString *)name; - -/*! - @abstract Constructs a new `PFRole` with the given name. - - @param name The name of the Role to create. - @param acl The ACL for this role. Roles must have an ACL. - */ -+ (instancetype)roleWithName:(NSString *)name acl:(PF_NULLABLE PFACL *)acl; - -///-------------------------------------- -/// @name Role-specific Properties -///-------------------------------------- - -/*! - @abstract Gets or sets the name for a role. - - @discussion This value must be set before the role has been saved to the server, - and cannot be set once the role has been saved. - - @warning A role's name can only contain alphanumeric characters, `_`, `-`, and spaces. - */ -@property (nonatomic, copy) NSString *name; - -/*! - @abstract Gets the for the objects that are direct children of this role. - - @discussion These users are granted any privileges that this role has been granted - (e.g. read or write access through ACLs). You can add or remove users from - the role through this relation. - */ -@property (nonatomic, strong, readonly) PFRelation *users; - -/*! - @abstract Gets the for the `PFRole` objects that are direct children of this role. - - @discussion These roles' users are granted any privileges that this role has been granted - (e.g. read or write access through ACLs). You can add or remove child roles - from this role through this relation. - */ -@property (nonatomic, strong, readonly) PFRelation *roles; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFSession.h b/Parse.framework/Headers/PFSession.h deleted file mode 100644 index 81247fd..0000000 --- a/Parse.framework/Headers/PFSession.h +++ /dev/null @@ -1,52 +0,0 @@ -// -// PFSession.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#else -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -@class PFSession; - -typedef void(^PFSessionResultBlock)(PFSession *PF_NULLABLE_S session, NSError *PF_NULLABLE_S error); - -/*! - `PFSession` is a local representation of a session. - This class is a subclass of a , - and retains the same functionality as any other subclass of . - */ -@interface PFSession : PFObject - -/*! - @abstract The session token string for this session. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy, readonly) NSString *sessionToken; - -/*! - *Asynchronously* fetches a `PFSession` object related to the current user. - - @returns A task that is `completed` with an instance of `PFSession` class or is `faulted` if the operation fails. - */ -+ (BFTask *)getCurrentSessionInBackground; - -/*! - *Asynchronously* fetches a `PFSession` object related to the current user. - - @param block The block to execute when the operation completes. - It should have the following argument signature: `^(PFSession *session, NSError *error)`. - */ -+ (void)getCurrentSessionInBackgroundWithBlock:(PF_NULLABLE PFSessionResultBlock)block; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFSubclassing.h b/Parse.framework/Headers/PFSubclassing.h deleted file mode 100644 index ce7e46c..0000000 --- a/Parse.framework/Headers/PFSubclassing.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// PFSubclassing.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if PARSE_IOS_ONLY -#import -#else -#import -#endif - -@class PFQuery; - -PF_ASSUME_NONNULL_BEGIN - -/*! - If a subclass of conforms to `PFSubclassing` and calls , - Parse framework will be able to use that class as the native class for a Parse cloud object. - - Classes conforming to this protocol should subclass and - include `PFObject+Subclass.h` in their implementation file. - This ensures the methods in the Subclass category of are exposed in its subclasses only. - */ -@protocol PFSubclassing - -/*! - @abstract Constructs an object of the most specific class known to implement . - - @discussion This method takes care to help subclasses be subclassed themselves. - For example, `[PFUser object]` returns a by default but will return an - object of a registered subclass instead if one is known. - A default implementation is provided by which should always be sufficient. - - @returns Returns the object that is instantiated. - */ -+ (instancetype)object; - -/*! - @abstract Creates a reference to an existing PFObject for use in creating associations between PFObjects. - - @discussion Calling <[PFObject isDataAvailable]> on this object will return `NO` - until <[PFObject fetchIfNeeded]> has been called. No network request will be made. - A default implementation is provided by PFObject which should always be sufficient. - - @param objectId The object id for the referenced object. - - @returns A new without data. - */ -+ (instancetype)objectWithoutDataWithObjectId:(PF_NULLABLE NSString *)objectId; - -/*! - @abstract The name of the class as seen in the REST API. - */ -+ (NSString *)parseClassName; - -/*! - @abstract Create a query which returns objects of this type. - - @discussion A default implementation is provided by which should always be sufficient. - */ -+ (PF_NULLABLE PFQuery *)query; - -/*! - @abstract Returns a query for objects of this type with a given predicate. - - @discussion A default implementation is provided by which should always be sufficient. - - @param predicate The predicate to create conditions from. - - @returns An instance of . - - @see [PFQuery queryWithClassName:predicate:] - */ -+ (PF_NULLABLE PFQuery *)queryWithPredicate:(PF_NULLABLE NSPredicate *)predicate; - -/*! - @abstract Lets Parse know this class should be used to instantiate all objects with class type . - - @warning This method must be called before <[Parse setApplicationId:clientKey:]> - */ -+ (void)registerSubclass; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFTwitterUtils.h b/Parse.framework/Headers/PFTwitterUtils.h deleted file mode 100644 index 2d10ff8..0000000 --- a/Parse.framework/Headers/PFTwitterUtils.h +++ /dev/null @@ -1,323 +0,0 @@ -// -// PFTwitterUtils.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#import -#import -#import - -PF_ASSUME_NONNULL_BEGIN - -@class BFTask; - -/*! - The `PFTwitterUtils` class provides utility functions for working with Twitter in a Parse application. - - This class is currently for iOS only. - */ -@interface PFTwitterUtils : NSObject - -///-------------------------------------- -/// @name Interacting With Twitter -///-------------------------------------- - -/*! - @abstract Gets the instance of the object that Parse uses. - - @returns An instance of object. - */ -+ (PF_NULLABLE PF_Twitter *)twitter; - -/*! - @abstract Initializes the Twitter singleton. - - @warning You must invoke this in order to use the Twitter functionality in Parse. - - @param consumerKey Your Twitter application's consumer key. - @param consumerSecret Your Twitter application's consumer secret. - */ -+ (void)initializeWithConsumerKey:(NSString *)consumerKey - consumerSecret:(NSString *)consumerSecret; - -/*! - @abstract Whether the user has their account linked to Twitter. - - @param user User to check for a Twitter link. The user must be logged in on this device. - - @returns `YES` if the user has their account linked to Twitter, otherwise `NO`. - */ -+ (BOOL)isLinkedWithUser:(PF_NULLABLE PFUser *)user; - -///-------------------------------------- -/// @name Logging In & Creating Twitter-Linked Users -///-------------------------------------- - -/*! - @abstract *Asynchronously* logs in a user using Twitter. - - @discussion This method delegates to Twitter to authenticate the user, - and then automatically logs in (or creates, in the case where it is a new user) a . - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)logInInBackground; - -/*! - @abstract *Asynchronously* logs in a user using Twitter. - - @discussion This method delegates to Twitter to authenticate the user, - and then automatically logs in (or creates, in the case where it is a new user) . - - @param block The block to execute. - It should have the following argument signature: `^(PFUser *user, NSError *error)`. - */ -+ (void)logInWithBlock:(PF_NULLABLE PFUserResultBlock)block; - -/* - @abstract *Asynchronously* Logs in a user using Twitter. - - @discussion This method delegates to Twitter to authenticate the user, - and then automatically logs in (or creates, in the case where it is a new user) a . - - @param target Target object for the selector - @param selector The selector that will be called when the asynchrounous request is complete. - It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError **)error`. - */ -+ (void)logInWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract *Asynchronously* logs in a user using Twitter. - - @discussion Allows you to handle user login to Twitter, then provide authentication - data to log in (or create, in the case where it is a new user) the . - - @param twitterId The id of the Twitter user being linked. - @param screenName The screen name of the Twitter user being linked. - @param authToken The auth token for the user's session. - @param authTokenSecret The auth token secret for the user's session. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)logInWithTwitterIdInBackground:(NSString *)twitterId - screenName:(NSString *)screenName - authToken:(NSString *)authToken - authTokenSecret:(NSString *)authTokenSecret; - -/*! - @abstract Logs in a user using Twitter. - - @discussion Allows you to handle user login to Twitter, then provide authentication data - to log in (or create, in the case where it is a new user) the . - - @param twitterId The id of the Twitter user being linked - @param screenName The screen name of the Twitter user being linked - @param authToken The auth token for the user's session - @param authTokenSecret The auth token secret for the user's session - @param block The block to execute. - It should have the following argument signature: `^(PFUser *user, NSError *error)`. - */ -+ (void)logInWithTwitterId:(NSString *)twitterId - screenName:(NSString *)screenName - authToken:(NSString *)authToken - authTokenSecret:(NSString *)authTokenSecret - block:(PF_NULLABLE PFUserResultBlock)block; - -/* - @abstract Logs in a user using Twitter. - - @discussion Allows you to handle user login to Twitter, then provide authentication data - to log in (or create, in the case where it is a new user) the . - - @param twitterId The id of the Twitter user being linked. - @param screenName The screen name of the Twitter user being linked. - @param authToken The auth token for the user's session. - @param authTokenSecret The auth token secret for the user's session. - @param target Target object for the selector. - @param selector The selector that will be called when the asynchronous request is complete. - It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError *)error`. - */ -+ (void)logInWithTwitterId:(NSString *)twitterId - screenName:(NSString *)screenName - authToken:(NSString *)authToken - authTokenSecret:(NSString *)authTokenSecret - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Linking Users with Twitter -///-------------------------------------- - -/*! - @abstract *Asynchronously* links Twitter to an existing PFUser. - - @discussion This method delegates to Twitter to authenticate the user, - and then automatically links the account to the . - - @param user User to link to Twitter. - - @deprecated Please use `[PFTwitterUtils linkUserInBackground:]` instead. - */ -+ (void)linkUser:(PFUser *)user PARSE_DEPRECATED("Please use +linkUserInBackground: instead."); - -/*! - @abstract *Asynchronously* links Twitter to an existing . - - @discussion This method delegates to Twitter to authenticate the user, - and then automatically links the account to the . - - @param user User to link to Twitter. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)linkUserInBackground:(PFUser *)user; - -/*! - @abstract *Asynchronously* links Twitter to an existing . - - @discussion This method delegates to Twitter to authenticate the user, - and then automatically links the account to the . - - @param user User to link to Twitter. - @param block The block to execute. - It should have the following argument signature: `^(BOOL *success, NSError *error)`. - */ -+ (void)linkUser:(PFUser *)user block:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract *Asynchronously* links Twitter to an existing . - - @discussion This method delegates to Twitter to authenticate the user, - and then automatically links the account to the . - - @param user User to link to Twitter. - @param target Target object for the selector - @param selector The selector that will be called when the asynchrounous request is complete. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - */ -+ (void)linkUser:(PFUser *)user - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract *Asynchronously* links Twitter to an existing PFUser asynchronously. - - @discussion Allows you to handle user login to Twitter, - then provide authentication data to link the account to the . - - @param user User to link to Twitter. - @param twitterId The id of the Twitter user being linked. - @param screenName The screen name of the Twitter user being linked. - @param authToken The auth token for the user's session. - @param authTokenSecret The auth token secret for the user's session. - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)linkUserInBackground:(PFUser *)user - twitterId:(NSString *)twitterId - screenName:(NSString *)screenName - authToken:(NSString *)authToken - authTokenSecret:(NSString *)authTokenSecret; - -/*! - @abstract *Asynchronously* links Twitter to an existing . - - @discussionAllows you to handle user login to Twitter, - then provide authentication data to link the account to the . - - @param user User to link to Twitter. - @param twitterId The id of the Twitter user being linked. - @param screenName The screen name of the Twitter user being linked. - @param authToken The auth token for the user's session. - @param authTokenSecret The auth token secret for the user's session. - @param block The block to execute. - It should have the following argument signature: `^(BOOL *success, NSError *error)`. - */ -+ (void)linkUser:(PFUser *)user - twitterId:(NSString *)twitterId - screenName:(NSString *)screenName - authToken:(NSString *)authToken - authTokenSecret:(NSString *)authTokenSecret - block:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract Links Twitter to an existing . - - @discussion This method allows you to handle user login to Twitter, - then provide authentication data to link the account to the . - - @param user User to link to Twitter. - @param twitterId The id of the Twitter user being linked. - @param screenName The screen name of the Twitter user being linked. - @param authToken The auth token for the user's session. - @param authTokenSecret The auth token secret for the user's session. - @param target Target object for the selector. - @param selector The selector that will be called when the asynchronous request is complete. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - */ -+ (void)linkUser:(PFUser *)user - twitterId:(NSString *)twitterId - screenName:(NSString *)screenName - authToken:(NSString *)authToken - authTokenSecret:(NSString *)authTokenSecret - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Unlinking Users from Twitter -///-------------------------------------- - -/*! - @abstract *Synchronously* unlinks the from a Twitter account. - - @param user User to unlink from Twitter. - - @returns Returns true if the unlink was successful. - */ -+ (BOOL)unlinkUser:(PFUser *)user; - -/*! - @abstract *Synchronously* unlinks the PFUser from a Twitter account. - - @param user User to unlink from Twitter. - @param error Error object to set on error. - - @returns Returns `YES` if the unlink was successful, otherwise `NO`. - */ -+ (BOOL)unlinkUser:(PFUser *)user error:(NSError **)error; - -/*! - @abstract Makes an *asynchronous* request to unlink a user from a Twitter account. - - @param user User to unlink from Twitter. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)unlinkUserInBackground:(PFUser *)user; - -/*! - @abstract Makes an *asynchronous* request to unlink a user from a Twitter account. - - @param user User to unlink from Twitter. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)unlinkUserInBackground:(PFUser *)user - block:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract Makes an *asynchronous* request to unlink a user from a Twitter account. - - @param user User to unlink from Twitter - @param target Target object for the selector - @param selector The selector that will be called when the asynchrounous request is complete. - */ -+ (void)unlinkUserInBackground:(PFUser *)user - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PFUser.h b/Parse.framework/Headers/PFUser.h deleted file mode 100644 index 0f66b2d..0000000 --- a/Parse.framework/Headers/PFUser.h +++ /dev/null @@ -1,455 +0,0 @@ -// -// PFUser.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE -#import -#import -#import -#else -#import -#import -#import -#endif - -PF_ASSUME_NONNULL_BEGIN - -typedef void(^PFUserSessionUpgradeResultBlock)(NSError *PF_NULLABLE_S error); -typedef void(^PFUserLogoutResultBlock)(NSError *PF_NULLABLE_S error); - - -@class PFQuery; - -/*! - The `PFUser` class is a local representation of a user persisted to the Parse Data. - This class is a subclass of a , and retains the same functionality of a , - but also extends it with various user specific methods, like authentication, signing up, and validation uniqueness. - - Many APIs responsible for linking a `PFUser` with Facebook or Twitter have been deprecated in favor of dedicated - utilities for each social network. See , and for more information. - */ - -@interface PFUser : PFObject - -///-------------------------------------- -/// @name Accessing the Current User -///-------------------------------------- - -/*! - @abstract Gets the currently logged in user from disk and returns an instance of it. - - @returns Returns a `PFUser` that is the currently logged in user. If there is none, returns `nil`. - */ -+ (PF_NULLABLE instancetype)currentUser; - -/*! - @abstract The session token for the `PFUser`. - - @discussion This is set by the server upon successful authentication. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy, readonly) NSString *sessionToken; - -/*! - @abstract Whether the `PFUser` was just created from a request. - - @discussion This is only set after a Facebook or Twitter login. - */ -@property (assign, readonly) BOOL isNew; - -/*! - @abstract Whether the user is an authenticated object for the device. - - @discussion An authenticated `PFUser` is one that is obtained via a or method. - An authenticated object is required in order to save (with altered values) or delete it. - - @returns Returns whether the user is authenticated. - */ -- (BOOL)isAuthenticated; - -///-------------------------------------- -/// @name Creating a New User -///-------------------------------------- - -/*! - @abstract Creates a new `PFUser` object. - - @returns Returns a new `PFUser` object. - */ -+ (PFUser *)user; - -/*! - @abstract Enables automatic creation of anonymous users. - - @discussion After calling this method, will always have a value. - The user will only be created on the server once the user has been saved, - or once an object with a relation to that user or an ACL that refers to the user has been saved. - - @warning <[PFObject saveEventually]> will not work on if an item being saved has a relation - to an automatic user that has never been saved. - */ -+ (void)enableAutomaticUser; - -/*! - @abstract The username for the `PFUser`. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *username; - -/**! - @abstract The password for the `PFUser`. - - @discussion This will not be filled in from the server with the password. - It is only meant to be set. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *password; - -/*! - @abstract The email for the `PFUser`. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, strong) NSString *email; - -/*! - @abstract Signs up the user *synchronously*. - - @discussion This will also enforce that the username isn't already taken. - - @warning Make sure that password and username are set before calling this method. - - @returns Returns `YES` if the sign up was successful, otherwise `NO`. - */ -- (BOOL)signUp; - -/*! - @abstract Signs up the user *synchronously*. - - @discussion This will also enforce that the username isn't already taken. - - @warning Make sure that password and username are set before calling this method. - - @param error Error object to set on error. - - @returns Returns whether the sign up was successful. - */ -- (BOOL)signUp:(NSError **)error; - -/*! - @abstract Signs up the user *asynchronously*. - - @discussion This will also enforce that the username isn't already taken. - - @warning Make sure that password and username are set before calling this method. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)signUpInBackground; - -/*! - @abstract Signs up the user *asynchronously*. - - @discussion This will also enforce that the username isn't already taken. - - @warning Make sure that password and username are set before calling this method. - - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -- (void)signUpInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block; - -/* - @abstract Signs up the user *asynchronously*. - - @discussion This will also enforce that the username isn't already taken. - - @warning Make sure that password and username are set before calling this method. - - @param target Target object for the selector. - @param selector The selector that will be called when the asynchrounous request is complete. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -- (void)signUpInBackgroundWithTarget:(PF_NULLABLE_S id)target selector:(PF_NULLABLE_S SEL)selector; - -///-------------------------------------- -/// @name Logging In -///-------------------------------------- - -/*! - @abstract Makes a *synchronous* request to login a user with specified credentials. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This also caches the user locally so that calls to will use the latest logged in user. - - @param username The username of the user. - @param password The password of the user. - - @returns Returns an instance of the `PFUser` on success. - If login failed for either wrong password or wrong username, returns `nil`. - */ -+ (PF_NULLABLE instancetype)logInWithUsername:(NSString *)username - password:(NSString *)password; - -/*! - @abstract Makes a *synchronous* request to login a user with specified credentials. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This also caches the user locally so that calls to will use the latest logged in user. - - @param username The username of the user. - @param password The password of the user. - @param error The error object to set on error. - - @returns Returns an instance of the `PFUser` on success. - If login failed for either wrong password or wrong username, returns `nil`. - */ -+ (PF_NULLABLE instancetype)logInWithUsername:(NSString *)username - password:(NSString *)password - error:(NSError **)error; - -/*! - @abstract Makes an *asynchronous* request to login a user with specified credentials. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This also caches the user locally so that calls to will use the latest logged in user. - - @param username The username of the user. - @param password The password of the user. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)logInWithUsernameInBackground:(NSString *)username - password:(NSString *)password; - -/* - @abstract Makes an *asynchronous* request to login a user with specified credentials. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This also caches the user locally so that calls to will use the latest logged in user. - - @param username The username of the user. - @param password The password of the user. - @param target Target object for the selector. - @param selector The selector that will be called when the asynchrounous request is complete. - It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError *)error`. - */ -+ (void)logInWithUsernameInBackground:(NSString *)username - password:(NSString *)password - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract Makes an *asynchronous* request to log in a user with specified credentials. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This also caches the user locally so that calls to will use the latest logged in user. - - @param username The username of the user. - @param password The password of the user. - @param block The block to execute. - It should have the following argument signature: `^(PFUser *user, NSError *error)`. - */ -+ (void)logInWithUsernameInBackground:(NSString *)username - password:(NSString *)password - block:(PF_NULLABLE PFUserResultBlock)block; - -///-------------------------------------- -/// @name Becoming a User -///-------------------------------------- - -/*! - @abstract Makes a *synchronous* request to become a user with the given session token. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This also caches the user locally so that calls to will use the latest logged in user. - - @param sessionToken The session token for the user. - - @returns Returns an instance of the `PFUser` on success. - If becoming a user fails due to incorrect token, it returns `nil`. - */ -+ (PF_NULLABLE instancetype)become:(NSString *)sessionToken; - -/*! - @abstract Makes a *synchronous* request to become a user with the given session token. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This will also cache the user locally so that calls to will use the latest logged in user. - - @param sessionToken The session token for the user. - @param error The error object to set on error. - - @returns Returns an instance of the `PFUser` on success. - If becoming a user fails due to incorrect token, it returns `nil`. - */ -+ (PF_NULLABLE instancetype)become:(NSString *)sessionToken error:(NSError **)error; - -/*! - @abstract Makes an *asynchronous* request to become a user with the given session token. - - @discussion Returns an instance of the successfully logged in `PFUser`. - This also caches the user locally so that calls to will use the latest logged in user. - - @param sessionToken The session token for the user. - - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)becomeInBackground:(NSString *)sessionToken; - -/* - @abstract Makes an *asynchronous* request to become a user with the given session token. - - @discussion Returns an instance of the successfully logged in `PFUser`. This also caches the user locally - so that calls to will use the latest logged in user. - - @param sessionToken The session token for the user. - @param target Target object for the selector. - @param selector The selector that will be called when the asynchrounous request is complete. - It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError *)error`. - */ -+ (void)becomeInBackground:(NSString *)sessionToken - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract Makes an *asynchronous* request to become a user with the given session token. - - @discussion Returns an instance of the successfully logged in `PFUser`. This also caches the user locally - so that calls to will use the latest logged in user. - - @param sessionToken The session token for the user. - @param block The block to execute. The block should have the following argument signature: (PFUser *user, NSError *error) - */ -+ (void)becomeInBackground:(NSString *)sessionToken block:(PF_NULLABLE PFUserResultBlock)block; - -///-------------------------------------- -/// @name Revocable Session -///-------------------------------------- - -/*! - @abstract Enables revocable sessions and migrates the currentUser session token to use revocable session if needed. - - @discussion This method is required if you want to use APIs - and you application's 'Require Revocable Session' setting is turned off on `http://parse.com` app settings. - After returned `BFTask` completes - class and APIs will be available for use. - - @returns An instance of `BFTask` that is completed when - revocable sessions are enabled and currentUser token is migrated. - */ -+ (BFTask *)enableRevocableSessionInBackground; - -/*! - @abstract Enables revocable sessions and upgrades the currentUser session token to use revocable session if needed. - - @discussion This method is required if you want to use APIs - and legacy sessions are enabled in your application settings on `http://parse.com/`. - After returned `BFTask` completes - class and APIs will be available for use. - - @param block Block that will be called when revocable sessions are enabled and currentUser token is migrated. - */ -+ (void)enableRevocableSessionInBackgroundWithBlock:(PF_NULLABLE PFUserSessionUpgradeResultBlock)block; - -///-------------------------------------- -/// @name Logging Out -///-------------------------------------- - -/*! - @abstract *Synchronously* logs out the currently logged in user on disk. - */ -+ (void)logOut; - -/*! - @abstract *Asynchronously* logs out the currenlt logged in user. - - @discussion This will also remove the session from disk, log out of linked services - and all future calls to will return `nil`. This is preferrable to using , - unless your code is already running from a background thread. - - @returns An instance of `BFTask`, that is resolved with `nil` result when logging out completes. - */ -+ (BFTask *)logOutInBackground; - -/*! - @abstract *Asynchronously* logs out the currenlt logged in user. - - @discussion This will also remove the session from disk, log out of linked services - and all future calls to will return `nil`. This is preferrable to using , - unless your code is already running from a background thread. - - @param block A block that will be called when logging out completes or fails. - */ -+ (void)logOutInBackgroundWithBlock:(PF_NULLABLE PFUserLogoutResultBlock)block; - -///-------------------------------------- -/// @name Requesting a Password Reset -///-------------------------------------- - -/*! - @abstract *Synchronously* Send a password reset request for a specified email. - - @discussion If a user account exists with that email, an email will be sent to that address - with instructions on how to reset their password. - - @param email Email of the account to send a reset password request. - - @returns Returns `YES` if the reset email request is successful. `NO` - if no account was found for the email address. - */ -+ (BOOL)requestPasswordResetForEmail:(NSString *)email; - -/*! - @abstract *Synchronously* send a password reset request for a specified email and sets an error object. - - @discussion If a user account exists with that email, an email will be sent to that address - with instructions on how to reset their password. - - @param email Email of the account to send a reset password request. - @param error Error object to set on error. - @returns Returns `YES` if the reset email request is successful. `NO` - if no account was found for the email address. - */ -+ (BOOL)requestPasswordResetForEmail:(NSString *)email - error:(NSError **)error; - -/*! - @abstract Send a password reset request asynchronously for a specified email and sets an - error object. If a user account exists with that email, an email will be sent to - that address with instructions on how to reset their password. - @param email Email of the account to send a reset password request. - @returns The task, that encapsulates the work being done. - */ -+ (BFTask *)requestPasswordResetForEmailInBackground:(NSString *)email; - -/* - @abstract Send a password reset request *asynchronously* for a specified email and sets an error object. - - @discussion If a user account exists with that email, an email will be sent to that address - with instructions on how to reset their password. - - @param email Email of the account to send a reset password request. - @param target Target object for the selector. - @param selector The selector that will be called when the asynchronous request is complete. - It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. - `error` will be `nil` on success and set if there was an error. - `[result boolValue]` will tell you whether the call succeeded or not. - */ -+ (void)requestPasswordResetForEmailInBackground:(NSString *)email - target:(PF_NULLABLE_S id)target - selector:(PF_NULLABLE_S SEL)selector; - -/*! - @abstract Send a password reset request *asynchronously* for a specified email. - - @discussion If a user account exists with that email, an email will be sent to that address - with instructions on how to reset their password. - - @param email Email of the account to send a reset password request. - @param block The block to execute. - It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. - */ -+ (void)requestPasswordResetForEmailInBackground:(NSString *)email - block:(PF_NULLABLE PFBooleanResultBlock)block; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/PF_Twitter.h b/Parse.framework/Headers/PF_Twitter.h deleted file mode 100644 index 16efbfd..0000000 --- a/Parse.framework/Headers/PF_Twitter.h +++ /dev/null @@ -1,83 +0,0 @@ -// -// PF_Twitter.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#import - -PF_ASSUME_NONNULL_BEGIN - -@class BFTask; - -/*! - The `PF_Twitter` class is a simple interface for interacting with the Twitter REST API, - automating sign-in and OAuth signing of requests against the API. - */ -@interface PF_Twitter : NSObject - -/*! - @abstract Consumer key of the application that is used to authorize with Twitter. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy) NSString *consumerKey; - -/*! - @abstract Consumer secret of the application that is used to authorize with Twitter. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy) NSString *consumerSecret; - -/*! - @abstract Auth token for the current user. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy) NSString *authToken; - -/*! - @abstract Auth token secret for the current user. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy) NSString *authTokenSecret; - -/*! - @abstract Twitter user id of the currently signed in user. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy) NSString *userId; - -/*! - @abstract Twitter screen name of the currently signed in user. - */ -@property (PF_NULLABLE_PROPERTY nonatomic, copy) NSString *screenName; - -/*! - @abstract Displays an auth dialog and populates the authToken, authTokenSecret, userId, and screenName properties - if the Twitter user grants permission to the application. - - @returns The task, that encapsulates the work being done. - */ -- (BFTask *)authorizeInBackground; - -/*! - @abstract Displays an auth dialog and populates the authToken, authTokenSecret, userId, and screenName properties - if the Twitter user grants permission to the application. - - @param success Invoked upon successful authorization. - @param failure Invoked upon an error occurring in the authorization process. - @param cancel Invoked when the user cancels authorization. - */ -- (void)authorizeWithSuccess:(PF_NULLABLE void (^)(void))success - failure:(PF_NULLABLE void (^)(NSError *PF_NULLABLE_S error))failure - cancel:(PF_NULLABLE void (^)(void))cancel; - -/*! - @abstract Adds a 3-legged OAuth signature to an `NSMutableURLRequest` based - upon the properties set for the Twitter object. - - @discussion Use this function to sign requests being made to the Twitter API. - - @param request Request to sign. - */ -- (void)signRequest:(PF_NULLABLE NSMutableURLRequest *)request; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Headers/Parse.h b/Parse.framework/Headers/Parse.h deleted file mode 100644 index 8045112..0000000 --- a/Parse.framework/Headers/Parse.h +++ /dev/null @@ -1,161 +0,0 @@ -// -// Parse.h -// -// Copyright 2011-present Parse Inc. All rights reserved. -// - -#import - -#if TARGET_OS_IPHONE - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#else - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#endif - -PF_ASSUME_NONNULL_BEGIN - -/*! - The `Parse` class contains static functions that handle global configuration for the Parse framework. - */ -@interface Parse : NSObject - -///-------------------------------------- -/// @name Connecting to Parse -///-------------------------------------- - -/*! - @abstract Sets the applicationId and clientKey of your application. - - @param applicationId The application id of your Parse application. - @param clientKey The client key of your Parse application. - */ -+ (void)setApplicationId:(NSString *)applicationId clientKey:(NSString *)clientKey; - -/*! - @abstract The current application id that was used to configure Parse framework. - */ -+ (NSString *)getApplicationId; - -/*! - @abstract The current client key that was used to configure Parse framework. - */ -+ (NSString *)getClientKey; - -///-------------------------------------- -/// @name Enabling Local Datastore -///-------------------------------------- - -/*! - @abstract Enable pinning in your application. This must be called before your application can use - pinning. The recommended way is to call this method before `setApplicationId:clientKey:`. - */ -+ (void)enableLocalDatastore; - -/*! - @abstract Flag that indicates whether Local Datastore is enabled. - - @returns `YES` if Local Datastore is enabled, otherwise `NO`. - */ -+ (BOOL)isLocalDatastoreEnabled; - -#if PARSE_IOS_ONLY - -///-------------------------------------- -/// @name Configuring UI Settings -///-------------------------------------- - -/*! - @abstract Set whether to show offline messages when using a Parse view or view controller related classes. - - @param enabled Whether a `UIAlertView` should be shown when the device is offline - and network access is required from a view or view controller. - - @deprecated This method has no effect. - */ -+ (void)offlineMessagesEnabled:(BOOL)enabled PARSE_DEPRECATED("This method is deprecated and has no effect."); - -/*! - @abstract Set whether to show an error message when using a Parse view or view controller related classes - and a Parse error was generated via a query. - - @param enabled Whether a `UIAlertView` should be shown when an error occurs. - - @deprecated This method has no effect. - */ -+ (void)errorMessagesEnabled:(BOOL)enabled PARSE_DEPRECATED("This method is deprecated and has no effect."); - -#endif - -///-------------------------------------- -/// @name Logging -///-------------------------------------- - -/*! - @abstract Sets the level of logging to display. - - @discussion By default: - - If running inside an app that was downloaded from iOS App Store - it is set to - - All other cases - it is set to - - @param logLevel Log level to set. - @see PFLogLevel - */ -+ (void)setLogLevel:(PFLogLevel)logLevel; - -/*! - @abstract Log level that will be displayed. - - @discussion By default: - - If running inside an app that was downloaded from iOS App Store - it is set to - - All other cases - it is set to - - @returns A value. - @see PFLogLevel - */ -+ (PFLogLevel)logLevel; - -@end - -PF_ASSUME_NONNULL_END diff --git a/Parse.framework/Info.plist b/Parse.framework/Info.plist deleted file mode 100644 index d5366ef..0000000 Binary files a/Parse.framework/Info.plist and /dev/null differ diff --git a/Parse.framework/Localizable.strings b/Parse.framework/Localizable.strings deleted file mode 100644 index e98bcb3..0000000 Binary files a/Parse.framework/Localizable.strings and /dev/null differ diff --git a/Parse.framework/Modules/module.modulemap b/Parse.framework/Modules/module.modulemap deleted file mode 100644 index 32a75e9..0000000 --- a/Parse.framework/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Parse { - umbrella header "Parse.h" - - export * - module * { export * } -} diff --git a/Parse.framework/Parse b/Parse.framework/Parse deleted file mode 100644 index fadb981..0000000 Binary files a/Parse.framework/Parse and /dev/null differ diff --git a/Parse.framework/third_party_licenses.txt b/Parse.framework/third_party_licenses.txt deleted file mode 100644 index dcea806..0000000 --- a/Parse.framework/third_party_licenses.txt +++ /dev/null @@ -1,92 +0,0 @@ -THE FOLLOWING SETS FORTH ATTRIBUTION NOTICES FOR THIRD PARTY SOFTWARE THAT MAY BE CONTAINED IN PORTIONS OF THE PARSE PRODUCT. - ------ - -The following software may be included in this product: AFNetworking. This software contains the following license and notice below: - -Copyright (c) 2011 Gowalla (http://gowalla.com/) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ------ - -The following software may be included in this product: OAuthCore. This software contains the following license and notice below: - -Copyright (C) 2012 Loren Brichter - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ------ - -The following software may be included in this product: google-breakpad. This software contains the following license and notice below: - -Copyright (c) 2006, Google Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. -* Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --------------------------------------------------------------------- - -Copyright 2001-2004 Unicode, Inc. - -Disclaimer - -This source code is provided as is by Unicode, Inc. No claims are -made as to fitness for any particular purpose. No warranties of any -kind are expressed or implied. The recipient agrees to determine -applicability of information provided. If this file has been -purchased on magnetic or optical media from Unicode, Inc., the -sole remedy for any claim will be exchange of defective media -within 90 days of receipt. - -Limitations on Rights to Redistribute This Code - -Unicode, Inc. hereby grants the right to freely use the information -supplied in this file in the creation of products supporting the -Unicode Standard, and to make copies of this file in any form -for internal or external distribution as long as this notice -remains attached. diff --git a/ParseCrashReporting.framework/Headers/ParseCrashReporting.h b/ParseCrashReporting.framework/Headers/ParseCrashReporting.h deleted file mode 100644 index 016f8f8..0000000 --- a/ParseCrashReporting.framework/Headers/ParseCrashReporting.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ParseCrashReporting.h -// Parse -// -// Created by Nikita Lutsenko on 8/6/14. -// Copyright (c) 2014 Parse Inc. All rights reserved. -// - -#import - -/*! - The `ParseCrashReporting` class is responsible for enabling crash reporting in your application. -*/ -@interface ParseCrashReporting : NSObject - -///-------------------------------------- -/// @name Crash Reporting -///-------------------------------------- - -/*! - @abstract Enables crash reporting for your app. - - @warning This must be called before you set Application ID and Client Key on Parse. - */ -+ (void)enable; - -/*! - @abstract Indicates whether crash reporting is currently enabled. - - @returns `YES` if crash reporting is enabled, `NO` - otherwise. - */ -+ (BOOL)isCrashReportingEnabled; - -@end diff --git a/ParseCrashReporting.framework/Info.plist b/ParseCrashReporting.framework/Info.plist deleted file mode 100644 index 74b5322..0000000 Binary files a/ParseCrashReporting.framework/Info.plist and /dev/null differ diff --git a/ParseCrashReporting.framework/Modules/module.modulemap b/ParseCrashReporting.framework/Modules/module.modulemap deleted file mode 100644 index 05c6588..0000000 --- a/ParseCrashReporting.framework/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module ParseCrashReporting { - umbrella header "ParseCrashReporting.h" - - export * - module * { export * } -} diff --git a/ParseCrashReporting.framework/ParseCrashReporting b/ParseCrashReporting.framework/ParseCrashReporting deleted file mode 100644 index bcff037..0000000 Binary files a/ParseCrashReporting.framework/ParseCrashReporting and /dev/null differ diff --git a/ParseCrashReporting.framework/third_party_licenses.txt b/ParseCrashReporting.framework/third_party_licenses.txt deleted file mode 100644 index dcea806..0000000 --- a/ParseCrashReporting.framework/third_party_licenses.txt +++ /dev/null @@ -1,92 +0,0 @@ -THE FOLLOWING SETS FORTH ATTRIBUTION NOTICES FOR THIRD PARTY SOFTWARE THAT MAY BE CONTAINED IN PORTIONS OF THE PARSE PRODUCT. - ------ - -The following software may be included in this product: AFNetworking. This software contains the following license and notice below: - -Copyright (c) 2011 Gowalla (http://gowalla.com/) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ------ - -The following software may be included in this product: OAuthCore. This software contains the following license and notice below: - -Copyright (C) 2012 Loren Brichter - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ------ - -The following software may be included in this product: google-breakpad. This software contains the following license and notice below: - -Copyright (c) 2006, Google Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. -* Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --------------------------------------------------------------------- - -Copyright 2001-2004 Unicode, Inc. - -Disclaimer - -This source code is provided as is by Unicode, Inc. No claims are -made as to fitness for any particular purpose. No warranties of any -kind are expressed or implied. The recipient agrees to determine -applicability of information provided. If this file has been -purchased on magnetic or optical media from Unicode, Inc., the -sole remedy for any claim will be exchange of defective media -within 90 days of receipt. - -Limitations on Rights to Redistribute This Code - -Unicode, Inc. hereby grants the right to freely use the information -supplied in this file in the creation of products supporting the -Unicode Standard, and to make copies of this file in any form -for internal or external distribution as long as this notice -remains attached. diff --git a/Podfile b/Podfile index 9d1ce45..99ca0d1 100644 --- a/Podfile +++ b/Podfile @@ -1,3 +1,22 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.1' -pod 'GoogleMaps' \ No newline at end of file + +platform :ios, '13.0' + +# Ensure pods use the same Swift version as the main target +use_frameworks! + +target 'vBox' do + pod 'GoogleMaps' +end + +target 'vBoxTests' do + inherit! :search_paths +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end + end +end diff --git a/vBox.xcodeproj/project.pbxproj b/vBox.xcodeproj/project.pbxproj index cc611d4..909800d 100644 --- a/vBox.xcodeproj/project.pbxproj +++ b/vBox.xcodeproj/project.pbxproj @@ -27,8 +27,6 @@ C19806111A2E84D9009AD73F /* UINavigationController+Orientation.m in Sources */ = {isa = PBXBuildFile; fileRef = C19806101A2E84D9009AD73F /* UINavigationController+Orientation.m */; }; C1A87CCE19F98EFC00845C7E /* DrivingHistoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1A87CCD19F98EFC00845C7E /* DrivingHistoryViewController.m */; }; C1B82C4319F0808E002E6EA4 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1B82C4219F0808E002E6EA4 /* CoreBluetooth.framework */; }; - C1C0636B1AE31C7F002D3847 /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C063691AE31C7F002D3847 /* Bolts.framework */; }; - C1C0636C1AE31C7F002D3847 /* Parse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C0636A1AE31C7F002D3847 /* Parse.framework */; }; C1C063731AE31CF2002D3847 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C0636D1AE31CF2002D3847 /* AudioToolbox.framework */; }; C1C063741AE31CF2002D3847 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C0636E1AE31CF2002D3847 /* CFNetwork.framework */; }; C1C063751AE31CF2002D3847 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C0636F1AE31CF2002D3847 /* libsqlite3.dylib */; }; @@ -38,7 +36,6 @@ C1C0637B1AE31D44002D3847 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C063791AE31D44002D3847 /* Accounts.framework */; }; C1C0637C1AE31D44002D3847 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C0637A1AE31D44002D3847 /* Social.framework */; }; C1C0637F1AE32B19002D3847 /* UtilityMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = C1C0637E1AE32B19002D3847 /* UtilityMethods.m */; }; - C1C063811AE35832002D3847 /* ParseCrashReporting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C063801AE35832002D3847 /* ParseCrashReporting.framework */; }; C1C063841AE35B8B002D3847 /* libstdc++.6.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C063831AE35B8B002D3847 /* libstdc++.6.dylib */; }; C1CEF69A19FA04D600D6E46D /* TripDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CEF69919FA04D600D6E46D /* TripDetailViewController.m */; }; C1D6CA5019F41A5A003FF61C /* BluetoothTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1D6CA4F19F41A5A003FF61C /* BluetoothTableViewController.m */; }; @@ -128,8 +125,6 @@ C1A87CCC19F98EFC00845C7E /* DrivingHistoryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrivingHistoryViewController.h; sourceTree = ""; }; C1A87CCD19F98EFC00845C7E /* DrivingHistoryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrivingHistoryViewController.m; sourceTree = ""; }; C1B82C4219F0808E002E6EA4 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; - C1C063691AE31C7F002D3847 /* Bolts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Bolts.framework; sourceTree = ""; }; - C1C0636A1AE31C7F002D3847 /* Parse.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Parse.framework; sourceTree = ""; }; C1C0636D1AE31CF2002D3847 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; C1C0636E1AE31CF2002D3847 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; C1C0636F1AE31CF2002D3847 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; @@ -140,7 +135,6 @@ C1C0637A1AE31D44002D3847 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; C1C0637D1AE32B19002D3847 /* UtilityMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UtilityMethods.h; sourceTree = ""; }; C1C0637E1AE32B19002D3847 /* UtilityMethods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UtilityMethods.m; sourceTree = ""; }; - C1C063801AE35832002D3847 /* ParseCrashReporting.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ParseCrashReporting.framework; sourceTree = ""; }; C1C063831AE35B8B002D3847 /* libstdc++.6.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libstdc++.6.dylib"; path = "usr/lib/libstdc++.6.dylib"; sourceTree = SDKROOT; }; C1C650711A07D76B000186E3 /* GPSInformation 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "GPSInformation 4.xcdatamodel"; sourceTree = ""; }; C1CCB1D81A0826CB00F0C3F5 /* GPSInformation 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "GPSInformation 6.xcdatamodel"; sourceTree = ""; }; @@ -210,12 +204,9 @@ C1E4587919DE0DC0001A5627 /* OpenGLES.framework in Frameworks */, C1E4587519DE0DAA001A5627 /* libicucore.dylib in Frameworks */, C1E4587319DE0D9C001A5627 /* libc++.dylib in Frameworks */, - C1C063811AE35832002D3847 /* ParseCrashReporting.framework in Frameworks */, C1E4587119DE0D92001A5627 /* ImageIO.framework in Frameworks */, C1E4586F19DE0D8A001A5627 /* GLKit.framework in Frameworks */, - C1C0636C1AE31C7F002D3847 /* Parse.framework in Frameworks */, C1E4586D19DE0D81001A5627 /* CoreText.framework in Frameworks */, - C1C0636B1AE31C7F002D3847 /* Bolts.framework in Frameworks */, C1E4586B19DE0D7A001A5627 /* CoreData.framework in Frameworks */, C1E4586919DE0D73001A5627 /* AVFoundation.framework in Frameworks */, 9DDAE7DA568EF4C4198F030F /* libPods.a in Frameworks */, @@ -419,7 +410,6 @@ children = ( C1C063831AE35B8B002D3847 /* libstdc++.6.dylib */, C147FA681AE472D300DC9E2A /* GoogleMaps.framework */, - C1C063801AE35832002D3847 /* ParseCrashReporting.framework */, C1C063791AE31D44002D3847 /* Accounts.framework */, C1C0637A1AE31D44002D3847 /* Social.framework */, C1C0636D1AE31CF2002D3847 /* AudioToolbox.framework */, @@ -428,8 +418,6 @@ C1C063701AE31CF2002D3847 /* MobileCoreServices.framework */, C1C063711AE31CF2002D3847 /* Security.framework */, C1C063721AE31CF2002D3847 /* StoreKit.framework */, - C1C063691AE31C7F002D3847 /* Bolts.framework */, - C1C0636A1AE31C7F002D3847 /* Parse.framework */, C122324B19F34052002318DF /* Foundation.framework */, C122324919F34030002318DF /* UIKit.framework */, C122324719F34021002318DF /* CoreGraphics.framework */, @@ -463,7 +451,6 @@ C1E4583A19DE0C5B001A5627 /* Frameworks */, C1E4583B19DE0C5B001A5627 /* Resources */, C15E76E71AB11DA500ADBBE8 /* Embed App Extensions */, - C1C063821AE35A78002D3847 /* ShellScript */, F03C2E43FA9EE505039055EB /* Copy Pods Resources */, ); buildRules = ( @@ -579,19 +566,6 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - C1C063821AE35A78002D3847 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export PATH=/usr/local/bin:$PATH\ncd /Users/rosbelsanroman/Developer/Xcode/vBox/parse/\n\nparse symbols -p \"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\""; - }; F03C2E43FA9EE505039055EB /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -704,7 +678,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + SWIFT_VERSION = 5.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = "-ObjC"; PROVISIONING_PROFILE = "be8aacc9-57c4-44d0-b4ae-fba4456eac54"; @@ -725,7 +700,8 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = vBox/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + SWIFT_VERSION = 5.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.rosbel.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -788,7 +764,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + SWIFT_VERSION = 5.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ObjC"; @@ -826,7 +803,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + SWIFT_VERSION = 5.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = "-ObjC"; PROVISIONING_PROFILE = "be8aacc9-57c4-44d0-b4ae-fba4456eac54"; @@ -847,7 +825,8 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = vBox/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + SWIFT_VERSION = 5.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.rosbel.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -867,7 +846,8 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = vBox/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + SWIFT_VERSION = 5.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.rosbel.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/vBox/AppDelegate.m b/vBox/AppDelegate.m index df4128a..cb2d39d 100644 --- a/vBox/AppDelegate.m +++ b/vBox/AppDelegate.m @@ -8,8 +8,6 @@ #import "AppDelegate.h" #import -#import -#import #import "UtilityMethods.h" @interface AppDelegate () @@ -25,33 +23,10 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. - - //old -// [GMSServices provideAPIKey:@"AIzaSyBdHnG4e7HZkd3RpXGWU6Sl0T2QL79kkyU"]; - - //new + + // Initialize Google Maps SDK [GMSServices provideAPIKey:@"AIzaSyCuezG1N1vEXjN8WweYUhdYGzGhOCkqNsE"]; - - [ParseCrashReporting enable]; - - [Parse setApplicationId:@"qRQ11sLN68WlCb2xIuV7YOfTDtxYKyq8I9rtXW8i" - clientKey:@"2ICp7TF5XM6bO40qS6IW8SD161wOqQ05VnGzmxyG"]; - - [self registerUserForNotifications:application]; - - if (application.applicationState != UIApplicationStateBackground) { - // Track an app open here if we launch with a push, unless - // "content_available" was used to trigger a background push (introduced - // in iOS 7). In that case, we skip tracking here to avoid double - // counting the app-open. - BOOL preBackgroundPush = ![application respondsToSelector:@selector(backgroundRefreshStatus)]; - BOOL oldPushHandlerOnly = ![self respondsToSelector:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]; - BOOL noPushPayload = ![launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; - if (preBackgroundPush || oldPushHandlerOnly || noPushPayload) { - [PFAnalytics trackAppOpenedWithLaunchOptionsInBackground:launchOptions block:nil]; - } - } - + return YES; } @@ -72,12 +47,6 @@ - (void)applicationWillEnterForeground:(UIApplication *)application { - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - PFInstallation *currentInstallation = [PFInstallation currentInstallation]; - if (currentInstallation.badge != 0) - { - currentInstallation.badge = 0; - [currentInstallation saveEventually]; - } } - (void)applicationWillTerminate:(UIApplication *)application { @@ -85,25 +54,6 @@ - (void)applicationWillTerminate:(UIApplication *)application { } -#pragma mark - Remote Notifications - --(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken -{ - PFInstallation *currentInstallation = [PFInstallation currentInstallation]; - [currentInstallation setDeviceTokenFromData:deviceToken]; - [currentInstallation saveInBackground]; -} - --(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo -{ - [PFPush handlePush:userInfo]; - if (application.applicationState == UIApplicationStateInactive) { - // The application was just brought from the background to the foreground, - // so we consider the app as having been "opened by a push notification." - [PFAnalytics trackAppOpenedWithRemoteNotificationPayload:userInfo]; - } -} - #pragma mark - Core Data -(void)saveContext @@ -201,14 +151,4 @@ - (NSURL *)applicationDocumentsDirectory return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; } -#pragma mark - Helper Methods -- (void)registerUserForNotifications:(UIApplication *)application { - UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert | - UIUserNotificationTypeBadge | - UIUserNotificationTypeSound); - UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:nil]; - [application registerUserNotificationSettings:settings]; - [application registerForRemoteNotifications]; -} - @end diff --git a/vBox/Base.lproj/Main.storyboard b/vBox/Base.lproj/Main.storyboard index ffe6cfe..d191c72 100644 --- a/vBox/Base.lproj/Main.storyboard +++ b/vBox/Base.lproj/Main.storyboard @@ -10,7 +10,7 @@ - + @@ -83,7 +83,7 @@ - + @@ -179,7 +179,7 @@ - + @@ -254,7 +254,7 @@ - + @@ -295,7 +295,7 @@ - + @@ -494,7 +494,7 @@ - + diff --git a/vBox/GoogleMapsViewController.m b/vBox/GoogleMapsViewController.m index f8d8fac..f6baf1c 100644 --- a/vBox/GoogleMapsViewController.m +++ b/vBox/GoogleMapsViewController.m @@ -10,7 +10,6 @@ #import "SVProgressHUD.h" #import "MyStyleKit.h" #import "UtilityMethods.h" -#import @interface GoogleMapsViewController () @@ -117,9 +116,7 @@ -(void)viewWillDisappear:(BOOL)animated [currentTrip setTotalMiles:@(GMSGeometryLength(completePath) * 0.000621371)]; [[appDelegate drivingHistory] addTripsObject:currentTrip]; [appDelegate saveContext]; - - [self reverseGeocodeAndTrackInBackground:prevLocation andAvgSpeed:avgSpeed]; - + // [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]; [super viewWillDisappear:animated]; } @@ -525,37 +522,6 @@ -(void)cleanUpBluetoothManager - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. - [PFAnalytics trackEventInBackground:@"MemoryWarning" dimensions:@{@"ViewController":@"GoogleMapsVC"} block:nil]; -} - -#pragma mark - Helper Methods - --(void)reverseGeocodeAndTrackInBackground:(CLLocation *)location andAvgSpeed:(double)avgSpeed -{ - CLGeocoder *geoCoder = [[CLGeocoder alloc] init]; - [geoCoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) { - if(error) - { - [PFAnalytics trackEventInBackground:@"ReverseGeoCodeError" dimensions:@{@"error":error.description} block:nil]; - } - else - { - CLPlacemark *placemark = placemarks.lastObject; - NSMutableDictionary *dimensions = [[NSMutableDictionary alloc] init]; - - dimensions[@"City"] = (NSString *) placemark.addressDictionary[@"City"]; - dimensions[@"State"] = (NSString *) placemark.addressDictionary[@"State"]; - dimensions[@"Country"] = (NSString *) placemark.addressDictionary[@"CountryCode"]; - dimensions[@"Street"] = (NSString *) placemark.addressDictionary[@"Street"]; - dimensions[@"StartTime"] = [UtilityMethods formattedStringFromDate:currentTrip.startTime]; - dimensions[@"MaxSpeed"] = [NSString stringWithFormat:@"%@ mph",@(maxSpeed)]; - dimensions[@"AvgSpeed"] = [NSString stringWithFormat:@"%@ mph",@(avgSpeed)]; - dimensions[@"Miles"] = [NSString stringWithFormat:@"%@ mi", currentTrip.totalMiles]; - - [PFAnalytics trackEventInBackground:@"TripEndDetail" dimensions:dimensions block:nil]; - } - }]; - } @end diff --git a/vBox/Info.plist b/vBox/Info.plist index 8d317ad..9f32d3e 100644 --- a/vBox/Info.plist +++ b/vBox/Info.plist @@ -26,12 +26,15 @@ Log your drive, even when not using app NSLocationWhenInUseUsageDescription This application will use your location only while it is being used. + NSBluetoothAlwaysUsageDescription + Connect to your OBD-II adapter to read vehicle diagnostics + NSBluetoothPeripheralUsageDescription + Connect to your OBD-II adapter to read vehicle diagnostics UIBackgroundModes bluetooth-central bluetooth-peripheral location - remote-notification UILaunchStoryboardName LaunchScreen @@ -39,7 +42,7 @@ Main UIRequiredDeviceCapabilities - armv7 + arm64 UIStatusBarStyle UIStatusBarStyleLightContent diff --git a/vBox/Managers/BLEManagerSwift.swift b/vBox/Managers/BLEManagerSwift.swift new file mode 100644 index 0000000..11dfa39 --- /dev/null +++ b/vBox/Managers/BLEManagerSwift.swift @@ -0,0 +1,552 @@ +// +// BLEManager.swift +// vBox +// +// Modern Swift implementation of Bluetooth Low Energy manager for OBD-II communication +// + +import Foundation +import CoreBluetooth +import Combine + +// MARK: - BLE Manager Delegate Protocol + +/// Swift protocol for BLE manager callbacks +@objc protocol BLEManagerSwiftDelegate: AnyObject { + /// Called when Bluetooth state changes + func bleManager(_ manager: BLEManagerSwift, didChangeState state: BLEState) + + /// Called when a diagnostic value is updated + func bleManager(_ manager: BLEManagerSwift, didUpdateDiagnostic key: String, value: NSNumber) + + /// Called when scanning begins + @objc optional func bleManagerDidBeginScanning(_ manager: BLEManagerSwift) + + /// Called when scanning stops + @objc optional func bleManagerDidStopScanning(_ manager: BLEManagerSwift) + + /// Called when a peripheral is connected + @objc optional func bleManagerDidConnect(_ manager: BLEManagerSwift) + + /// Called when a peripheral is disconnected + @objc optional func bleManagerDidDisconnect(_ manager: BLEManagerSwift) + + /// Called with debug log messages + @objc optional func bleManager(_ manager: BLEManagerSwift, didLogMessage message: String) +} + +// MARK: - BLE Manager Swift + +/// Modern Swift implementation of BLE manager for OBD-II adapters +@objc class BLEManagerSwift: NSObject { + + // MARK: - Singleton + + @objc static let shared = BLEManagerSwift() + + // MARK: - Published Properties (Combine) + + /// Current Bluetooth state + @Published private(set) var state: BLEState = .unknown + + /// Whether currently connected to a peripheral + @Published private(set) var isConnected: Bool = false + + /// Whether currently scanning + @Published private(set) var isScanning: Bool = false + + /// Current vehicle diagnostics + @Published private(set) var diagnostics = VehicleDiagnostics() + + /// Discovered peripherals during scanning + @Published private(set) var discoveredPeripherals: [DiscoveredPeripheral] = [] + + // MARK: - Delegate + + @objc weak var delegate: BLEManagerSwiftDelegate? + + // MARK: - Private Properties + + private var centralManager: CBCentralManager! + private var peripheralManager: CBPeripheralManager? + private var connectedPeripheral: CBPeripheral? + private var targetServiceUUID: CBUUID? + private var dataCharacteristic: CBCharacteristic? + + private let centralQueue = DispatchQueue(label: "com.vbox.ble.central", qos: .userInitiated) + private let peripheralQueue = DispatchQueue(label: "com.vbox.ble.peripheral", qos: .background) + + private var cancellables = Set() + + // For BeagleBone peripheral mode + private var advertisingCharacteristic: CBMutableCharacteristic? + + // MARK: - Initialization + + override init() { + super.init() + + centralManager = CBCentralManager( + delegate: self, + queue: centralQueue, + options: [CBCentralManagerOptionShowPowerAlertKey: true] + ) + + // Set up peripheral manager if BeagleBone connection is enabled + if UserDefaults.standard.bool(forKey: "shouldConnectToBeagleBone") { + peripheralManager = CBPeripheralManager( + delegate: self, + queue: peripheralQueue, + options: [CBPeripheralManagerOptionShowPowerAlertKey: false] + ) + } + } + + // MARK: - Public Methods + + /// Start scanning for peripherals of the specified type + /// - Parameter type: The type of peripheral to scan for + /// - Returns: true if scanning started, false if Bluetooth is not ready + @objc @discardableResult + func scan(for type: PeripheralType) -> Bool { + guard state == .on else { + log("Cannot scan: Bluetooth state is \(state.description)") + return false + } + + targetServiceUUID = type.serviceUUID + discoveredPeripherals.removeAll() + + log("Scanning for \(type.rawValue) peripherals...") + + centralManager.scanForPeripherals( + withServices: [type.serviceUUID], + options: [CBCentralManagerScanOptionAllowDuplicatesKey: false] + ) + + isScanning = true + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManagerDidBeginScanning?(self) + } + + return true + } + + /// Stop scanning for peripherals + @objc func stopScanning() { + centralManager.stopScan() + isScanning = false + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManagerDidStopScanning?(self) + } + + log("Stopped scanning") + } + + /// Connect to a discovered peripheral + /// - Parameter peripheral: The peripheral to connect to + func connect(to peripheral: DiscoveredPeripheral) { + // Find the CBPeripheral from our discovered list + // In a real implementation, we'd store the CBPeripheral reference + log("Connecting to \(peripheral.displayName)...") + } + + /// Connect to a CBPeripheral directly + @objc func connect(to peripheral: CBPeripheral) { + log("Connecting to \(peripheral.name ?? "Unknown")...") + + centralManager.connect( + peripheral, + options: [CBConnectPeripheralOptionNotifyOnDisconnectionKey: true] + ) + } + + /// Disconnect from the current peripheral + @objc func disconnect() { + guard let peripheral = connectedPeripheral else { + log("No peripheral connected") + return + } + + if peripheral.state == .connected { + centralManager.cancelPeripheralConnection(peripheral) + } + } + + /// Enable or disable notifications for the data characteristic + /// - Parameter enabled: Whether to enable notifications + @objc func setNotifications(enabled: Bool) { + guard let peripheral = connectedPeripheral, + let characteristic = dataCharacteristic, + peripheral.state == .connected else { + log("Cannot set notifications: not connected") + return + } + + peripheral.setNotifyValue(enabled, for: characteristic) + log("Notifications \(enabled ? "enabled" : "disabled")") + } + + /// Stop advertising as a peripheral (BeagleBone mode) + @objc func stopAdvertising() { + guard let manager = peripheralManager, manager.isAdvertising else { + return + } + + manager.stopAdvertising() + log("Stopped advertising") + } + + // MARK: - Private Methods + + private func log(_ message: String) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManager?(self, didLogMessage: message) + } + + #if DEBUG + print("[BLE] \(message)") + #endif + } + + private func updateState(_ newState: BLEState) { + state = newState + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManager(self, didChangeState: newState) + } + } + + private func processReceivedData(_ data: Data) { + guard let packet = BLEDataPacket(data: data) else { + log("Invalid packet received (checksum failed or invalid format)") + return + } + + guard let pid = packet.obdPID else { + log("Unknown PID: 0x\(String(format: "%X", packet.pid)) = \(packet.primaryValue)") + return + } + + let reading = DiagnosticReading(pid: pid, value: packet.primaryValue) + + guard reading.isValid else { + log("Invalid reading for \(pid.displayName): \(packet.primaryValue)") + return + } + + // Update our diagnostics snapshot + diagnostics.update(with: reading) + + // Notify delegate + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManager( + self, + didUpdateDiagnostic: pid.displayName, + value: NSNumber(value: reading.value) + ) + } + } +} + +// MARK: - CBCentralManagerDelegate + +extension BLEManagerSwift: CBCentralManagerDelegate { + + func centralManagerDidUpdateState(_ central: CBCentralManager) { + let newState: BLEState + + switch central.state { + case .poweredOn: + newState = .on + case .poweredOff: + newState = .off + case .resetting: + newState = .resetting + case .unauthorized: + newState = .unauthorized + case .unsupported: + newState = .unsupported + case .unknown: + newState = .unknown + @unknown default: + newState = .unknown + } + + log("Bluetooth state changed: \(newState.description)") + updateState(newState) + } + + func centralManager(_ central: CBCentralManager, + didDiscover peripheral: CBPeripheral, + advertisementData: [String: Any], + rssi RSSI: NSNumber) { + + let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String + + guard localName != nil && !localName!.isEmpty else { + return + } + + log("Discovered: \(localName ?? peripheral.identifier.uuidString) (RSSI: \(RSSI))") + + // Stop scanning and connect + centralManager.stopScan() + isScanning = false + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManagerDidStopScanning?(self) + } + + connectedPeripheral = peripheral + peripheral.delegate = self + + centralManager.connect( + peripheral, + options: [CBConnectPeripheralOptionNotifyOnDisconnectionKey: true] + ) + } + + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + log("Connected to \(peripheral.name ?? "Unknown")") + + isConnected = true + peripheral.delegate = self + peripheral.discoverServices(nil) + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManagerDidConnect?(self) + } + } + + func centralManager(_ central: CBCentralManager, + didDisconnectPeripheral peripheral: CBPeripheral, + error: Error?) { + + log("Disconnected from \(peripheral.name ?? "Unknown")") + + isConnected = false + connectedPeripheral = nil + dataCharacteristic = nil + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.bleManagerDidDisconnect?(self) + } + } + + func centralManager(_ central: CBCentralManager, + didFailToConnect peripheral: CBPeripheral, + error: Error?) { + + log("Failed to connect: \(error?.localizedDescription ?? "Unknown error")") + connectedPeripheral = nil + } +} + +// MARK: - CBPeripheralDelegate + +extension BLEManagerSwift: CBPeripheralDelegate { + + func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + guard error == nil else { + log("Error discovering services: \(error!.localizedDescription)") + return + } + + guard let services = peripheral.services else { + log("No services found") + return + } + + log("Discovered \(services.count) service(s)") + + for service in services { + log("Service: \(service.uuid)") + peripheral.discoverCharacteristics(nil, for: service) + } + } + + func peripheral(_ peripheral: CBPeripheral, + didDiscoverCharacteristicsFor service: CBService, + error: Error?) { + + guard error == nil else { + log("Error discovering characteristics: \(error!.localizedDescription)") + return + } + + guard let characteristics = service.characteristics else { + log("No characteristics found for service \(service.uuid)") + return + } + + log("Discovered \(characteristics.count) characteristic(s)") + + for characteristic in characteristics { + log("Characteristic: \(characteristic.uuid)") + + // Subscribe to notifications + if characteristic.properties.contains(.notify) { + peripheral.setNotifyValue(true, for: characteristic) + dataCharacteristic = characteristic + } + } + } + + func peripheral(_ peripheral: CBPeripheral, + didUpdateValueFor characteristic: CBCharacteristic, + error: Error?) { + + if let error = error { + log("Error receiving data: \(error.localizedDescription)") + return + } + + guard let data = characteristic.value else { + log("No data received") + return + } + + processReceivedData(data) + } + + func peripheral(_ peripheral: CBPeripheral, + didUpdateNotificationStateFor characteristic: CBCharacteristic, + error: Error?) { + + if let error = error { + log("Error updating notification state: \(error.localizedDescription)") + return + } + + log("Notifications \(characteristic.isNotifying ? "enabled" : "disabled") for \(characteristic.uuid)") + } +} + +// MARK: - CBPeripheralManagerDelegate + +extension BLEManagerSwift: CBPeripheralManagerDelegate { + + func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { + switch peripheral.state { + case .poweredOn: + setupPeripheralService() + default: + break + } + } + + private func setupPeripheralService() { + guard let manager = peripheralManager else { return } + + let serviceUUID = CBUUID(string: "FFEF") + let characteristicUUID = CBUUID(string: "FFE1") + + let characteristic = CBMutableCharacteristic( + type: characteristicUUID, + properties: [.read, .write, .notify, .writeWithoutResponse], + value: nil, + permissions: [.readable, .writeable] + ) + + advertisingCharacteristic = characteristic + + let service = CBMutableService(type: serviceUUID, primary: true) + service.characteristics = [characteristic] + + manager.add(service) + + manager.startAdvertising([ + CBAdvertisementDataLocalNameKey: "vBox", + CBAdvertisementDataServiceUUIDsKey: [serviceUUID] + ]) + + log("Started advertising as peripheral") + } + + func peripheralManager(_ peripheral: CBPeripheralManager, + central: CBCentral, + didSubscribeTo characteristic: CBCharacteristic) { + log("Central subscribed to characteristic") + } + + func peripheralManager(_ peripheral: CBPeripheralManager, + didReceiveRead request: CBATTRequest) { + + let value = advertisingCharacteristic?.value ?? "vBox".data(using: .utf8)! + request.value = value + peripheral.respond(to: request, withResult: .success) + } + + func peripheralManager(_ peripheral: CBPeripheralManager, + didReceiveWrite requests: [CBATTRequest]) { + + for request in requests { + advertisingCharacteristic?.value = request.value + peripheral.respond(to: request, withResult: .success) + } + } + + func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) { + guard let characteristic = advertisingCharacteristic, + let value = characteristic.value else { + return + } + + peripheral.updateValue(value, for: characteristic, onSubscribedCentrals: nil) + } +} + +// MARK: - Combine Extensions + +extension BLEManagerSwift { + + /// Publisher for state changes + var statePublisher: AnyPublisher { + $state.eraseToAnyPublisher() + } + + /// Publisher for connection status changes + var connectionPublisher: AnyPublisher { + $isConnected.eraseToAnyPublisher() + } + + /// Publisher for diagnostic updates + var diagnosticsPublisher: AnyPublisher { + $diagnostics.eraseToAnyPublisher() + } + + /// Publisher for speed values + var speedPublisher: AnyPublisher { + $diagnostics + .map { $0.speed } + .removeDuplicates() + .eraseToAnyPublisher() + } + + /// Publisher for RPM values + var rpmPublisher: AnyPublisher { + $diagnostics + .map { $0.rpm } + .removeDuplicates() + .eraseToAnyPublisher() + } + + /// Publisher for fuel level values + var fuelLevelPublisher: AnyPublisher { + $diagnostics + .map { $0.fuelLevel } + .removeDuplicates() + .eraseToAnyPublisher() + } +} diff --git a/vBox/Models/BLETypes.swift b/vBox/Models/BLETypes.swift new file mode 100644 index 0000000..9d953a1 --- /dev/null +++ b/vBox/Models/BLETypes.swift @@ -0,0 +1,264 @@ +// +// BLETypes.swift +// vBox +// +// Swift types for Bluetooth Low Energy management +// + +import Foundation +import CoreBluetooth + +// MARK: - BLE State + +/// Represents the current state of the Bluetooth adapter +enum BLEState: Int, CustomStringConvertible { + case unknown = 0 + case resetting = 1 + case unsupported = 2 + case unauthorized = 3 + case off = 4 + case on = 5 + + /// Initialize from CoreBluetooth manager state + init(from cbState: CBManagerState) { + switch cbState { + case .unknown: self = .unknown + case .resetting: self = .resetting + case .unsupported: self = .unsupported + case .unauthorized: self = .unauthorized + case .poweredOff: self = .off + case .poweredOn: self = .on + @unknown default: self = .unknown + } + } + + var description: String { + switch self { + case .unknown: return "Unknown" + case .resetting: return "Resetting" + case .unsupported: return "Unsupported" + case .unauthorized: return "Unauthorized" + case .off: return "Off" + case .on: return "On" + } + } + + /// Whether Bluetooth is ready for scanning/connecting + var isReady: Bool { + return self == .on + } + + /// Whether there's an issue that needs user attention + var requiresUserAction: Bool { + switch self { + case .unsupported, .unauthorized, .off: + return true + default: + return false + } + } + + /// User-friendly message describing the state + var userMessage: String { + switch self { + case .unknown: + return "Bluetooth status is unknown" + case .resetting: + return "Bluetooth is resetting..." + case .unsupported: + return "This device does not support Bluetooth Low Energy" + case .unauthorized: + return "Please authorize Bluetooth access in Settings" + case .off: + return "Please turn on Bluetooth in Settings" + case .on: + return "Bluetooth is ready" + } + } +} + +// MARK: - Peripheral Type + +/// Types of peripherals the app can connect to +enum PeripheralType: String, CaseIterable { + case obdAdapter = "OBD" + case beagleBone = "BeagleBone" + + /// The BLE service UUID to scan for + var serviceUUID: CBUUID { + switch self { + case .obdAdapter: + return CBUUID(string: "FFE0") + case .beagleBone: + return CBUUID(string: "FFEF") + } + } + + /// The characteristic UUID for data transfer + var characteristicUUID: CBUUID { + return CBUUID(string: "FFE1") + } +} + +// MARK: - Connection State + +/// Represents the connection state of a peripheral +enum ConnectionState { + case disconnected + case connecting + case connected + case disconnecting + + /// Initialize from CoreBluetooth peripheral state + init(from cbState: CBPeripheralState) { + switch cbState { + case .disconnected: self = .disconnected + case .connecting: self = .connecting + case .connected: self = .connected + case .disconnecting: self = .disconnecting + @unknown default: self = .disconnected + } + } + + var isConnected: Bool { + return self == .connected + } +} + +// MARK: - Discovered Peripheral + +/// Information about a discovered BLE peripheral +struct DiscoveredPeripheral { + let identifier: UUID + let name: String? + let rssi: Int + let advertisementData: [String: Any] + let discoveredAt: Date + + init(peripheral: CBPeripheral, rssi: NSNumber, advertisementData: [String: Any]) { + self.identifier = peripheral.identifier + self.name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String + self.rssi = rssi.intValue + self.advertisementData = advertisementData + self.discoveredAt = Date() + } + + /// Display name for the peripheral + var displayName: String { + return name ?? "Unknown Device" + } + + /// Signal strength description + var signalStrength: SignalStrength { + return SignalStrength(rssi: rssi) + } +} + +// MARK: - Signal Strength + +/// Categorization of BLE signal strength +enum SignalStrength: CustomStringConvertible { + case excellent // > -50 dBm + case good // -50 to -70 dBm + case fair // -70 to -80 dBm + case weak // < -80 dBm + + init(rssi: Int) { + switch rssi { + case -50...: self = .excellent + case -70 ..< -50: self = .good + case -80 ..< -70: self = .fair + default: self = .weak + } + } + + var description: String { + switch self { + case .excellent: return "Excellent" + case .good: return "Good" + case .fair: return "Fair" + case .weak: return "Weak" + } + } + + /// Number of bars to show (0-4) + var bars: Int { + switch self { + case .excellent: return 4 + case .good: return 3 + case .fair: return 2 + case .weak: return 1 + } + } +} + +// MARK: - BLE Error + +/// Errors that can occur during BLE operations +enum BLEError: LocalizedError { + case bluetoothOff + case bluetoothUnauthorized + case bluetoothUnsupported + case notConnected + case connectionFailed(Error?) + case serviceNotFound + case characteristicNotFound + case writeError(Error?) + case invalidData + + var errorDescription: String? { + switch self { + case .bluetoothOff: + return "Bluetooth is turned off" + case .bluetoothUnauthorized: + return "Bluetooth access is not authorized" + case .bluetoothUnsupported: + return "Bluetooth Low Energy is not supported on this device" + case .notConnected: + return "Not connected to a device" + case .connectionFailed(let error): + return "Connection failed: \(error?.localizedDescription ?? "Unknown error")" + case .serviceNotFound: + return "Required BLE service not found" + case .characteristicNotFound: + return "Required BLE characteristic not found" + case .writeError(let error): + return "Failed to write data: \(error?.localizedDescription ?? "Unknown error")" + case .invalidData: + return "Received invalid data" + } + } +} + +// MARK: - BLE Manager Delegate Protocol + +/// Protocol for receiving BLE manager updates +protocol BLEManagerDelegate: AnyObject { + /// Called when Bluetooth state changes + func bleManager(_ manager: Any, didUpdateState state: BLEState) + + /// Called when a peripheral is discovered + func bleManager(_ manager: Any, didDiscover peripheral: DiscoveredPeripheral) + + /// Called when connected to a peripheral + func bleManager(_ manager: Any, didConnect peripheral: DiscoveredPeripheral) + + /// Called when disconnected from a peripheral + func bleManager(_ manager: Any, didDisconnect peripheral: DiscoveredPeripheral, error: Error?) + + /// Called when a diagnostic value is received + func bleManager(_ manager: Any, didReceive reading: DiagnosticReading) + + /// Called for debug logging + func bleManager(_ manager: Any, didLog message: String) +} + +// MARK: - Default Implementations + +extension BLEManagerDelegate { + func bleManager(_ manager: Any, didDiscover peripheral: DiscoveredPeripheral) {} + func bleManager(_ manager: Any, didConnect peripheral: DiscoveredPeripheral) {} + func bleManager(_ manager: Any, didDisconnect peripheral: DiscoveredPeripheral, error: Error?) {} + func bleManager(_ manager: Any, didReceive reading: DiagnosticReading) {} + func bleManager(_ manager: Any, didLog message: String) {} +} diff --git a/vBox/Models/CoreData/BluetoothDataEntity.swift b/vBox/Models/CoreData/BluetoothDataEntity.swift new file mode 100644 index 0000000..ab1b790 --- /dev/null +++ b/vBox/Models/CoreData/BluetoothDataEntity.swift @@ -0,0 +1,299 @@ +// +// BluetoothDataEntity.swift +// vBox +// +// Swift Core Data model for BluetoothData entity +// + +import Foundation +import CoreData + +// MARK: - Bluetooth Data Entity + +@objc(BluetoothDataEntity) +public class BluetoothDataEntity: NSManagedObject { + + // MARK: - Core Data Properties + + @NSManaged public var speed: NSNumber? + @NSManaged public var rpm: NSNumber? + @NSManaged public var fuel: NSNumber? + @NSManaged public var coolantTemp: NSNumber? + @NSManaged public var engineLoad: NSNumber? + @NSManaged public var throttle: NSNumber? + @NSManaged public var intakeTemp: NSNumber? + @NSManaged public var ambientTemp: NSNumber? + @NSManaged public var barometric: NSNumber? + @NSManaged public var distance: NSNumber? + @NSManaged public var accelX: NSNumber? + @NSManaged public var accelY: NSNumber? + @NSManaged public var accelZ: NSNumber? + @NSManaged public var location: GPSLocationEntity? +} + +// MARK: - Fetch Request + +extension BluetoothDataEntity { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "BluetoothData") + } +} + +// MARK: - Computed Properties + +extension BluetoothDataEntity { + + // MARK: Speed & Engine + + /// OBD speed in km/h + var speedKmh: Double? { + return speed?.doubleValue + } + + /// OBD speed in mph + var speedMph: Double? { + guard let kmh = speedKmh else { return nil } + return kmh * 0.621371 + } + + /// Engine RPM + var engineRpm: Double? { + return rpm?.doubleValue + } + + /// Engine load percentage (0-100) + var engineLoadPercentage: Double? { + return engineLoad?.doubleValue + } + + /// Throttle position percentage (0-100) + var throttlePosition: Double? { + return throttle?.doubleValue + } + + // MARK: Fuel + + /// Fuel level percentage (0-100) + var fuelLevel: Double? { + return fuel?.doubleValue + } + + /// Whether fuel is low (below 15%) + var isFuelLow: Bool { + guard let level = fuelLevel else { return false } + return level < 15 + } + + // MARK: Temperatures + + /// Coolant temperature in Celsius + var coolantTemperature: Double? { + return coolantTemp?.doubleValue + } + + /// Coolant temperature in Fahrenheit + var coolantTemperatureFahrenheit: Double? { + guard let celsius = coolantTemperature else { return nil } + return celsius * 9/5 + 32 + } + + /// Intake air temperature in Celsius + var intakeTemperature: Double? { + return intakeTemp?.doubleValue + } + + /// Ambient temperature in Celsius + var ambientTemperature: Double? { + return ambientTemp?.doubleValue + } + + /// Whether engine is overheating (coolant > 105°C) + var isOverheating: Bool { + guard let temp = coolantTemperature else { return false } + return temp > 105 + } + + /// Whether engine is at normal operating temperature (80-100°C) + var isAtOperatingTemperature: Bool { + guard let temp = coolantTemperature else { return false } + return temp >= 80 && temp <= 100 + } + + // MARK: Pressure + + /// Barometric pressure in kPa + var barometricPressure: Double? { + return barometric?.doubleValue + } + + // MARK: Distance + + /// OBD distance traveled + var distanceTraveled: Double? { + return distance?.doubleValue + } + + // MARK: Accelerometer + + /// X-axis acceleration + var accelerationX: Double? { + return accelX?.doubleValue + } + + /// Y-axis acceleration + var accelerationY: Double? { + return accelY?.doubleValue + } + + /// Z-axis acceleration + var accelerationZ: Double? { + return accelZ?.doubleValue + } + + /// Total acceleration magnitude + var accelerationMagnitude: Double? { + guard let x = accelerationX, + let y = accelerationY, + let z = accelerationZ else { + return nil + } + return sqrt(x * x + y * y + z * z) + } + + /// Accelerometer reading as tuple + var acceleration: (x: Double, y: Double, z: Double)? { + guard let x = accelerationX, + let y = accelerationY, + let z = accelerationZ else { + return nil + } + return (x, y, z) + } +} + +// MARK: - Update from Diagnostics + +extension BluetoothDataEntity { + + /// Update from a VehicleDiagnostics snapshot + func update(from diagnostics: VehicleDiagnostics) { + if let value = diagnostics.speed { + speed = NSNumber(value: value) + } + if let value = diagnostics.rpm { + rpm = NSNumber(value: value) + } + if let value = diagnostics.fuelLevel { + fuel = NSNumber(value: value) + } + if let value = diagnostics.coolantTemp { + coolantTemp = NSNumber(value: value) + } + if let value = diagnostics.engineLoad { + engineLoad = NSNumber(value: value) + } + if let value = diagnostics.throttle { + throttle = NSNumber(value: value) + } + if let value = diagnostics.intakeTemp { + intakeTemp = NSNumber(value: value) + } + if let value = diagnostics.ambientTemp { + ambientTemp = NSNumber(value: value) + } + if let value = diagnostics.barometric { + barometric = NSNumber(value: value) + } + if let value = diagnostics.distance { + distance = NSNumber(value: value) + } + } + + /// Update from a diagnostic reading + func update(from reading: DiagnosticReading) { + switch reading.pid { + case .speed: + speed = NSNumber(value: reading.value) + case .rpm: + rpm = NSNumber(value: reading.value) + case .fuelLevel: + fuel = NSNumber(value: reading.value) + case .coolantTemp: + coolantTemp = NSNumber(value: reading.value) + case .engineLoad: + engineLoad = NSNumber(value: reading.value) + case .throttle: + throttle = NSNumber(value: reading.value) + case .intakeTemp: + intakeTemp = NSNumber(value: reading.value) + case .ambientTemp: + ambientTemp = NSNumber(value: reading.value) + case .barometric: + barometric = NSNumber(value: reading.value) + case .distance: + distance = NSNumber(value: reading.value) + default: + break + } + } + + /// Update accelerometer values + func updateAccelerometer(x: Double, y: Double, z: Double) { + accelX = NSNumber(value: x) + accelY = NSNumber(value: y) + accelZ = NSNumber(value: z) + } +} + +// MARK: - Factory + +extension BluetoothDataEntity { + + /// Create a new BluetoothData entity + static func create(in context: NSManagedObjectContext) -> BluetoothDataEntity { + return BluetoothDataEntity(context: context) + } + + /// Create a new BluetoothData entity with initial values + static func create( + in context: NSManagedObjectContext, + speed: Double? = nil, + rpm: Double? = nil, + fuel: Double? = nil, + coolantTemp: Double? = nil + ) -> BluetoothDataEntity { + let entity = BluetoothDataEntity(context: context) + if let value = speed { entity.speed = NSNumber(value: value) } + if let value = rpm { entity.rpm = NSNumber(value: value) } + if let value = fuel { entity.fuel = NSNumber(value: value) } + if let value = coolantTemp { entity.coolantTemp = NSNumber(value: value) } + return entity + } +} + +// MARK: - Dictionary Export + +extension BluetoothDataEntity { + + /// Export all values as a dictionary (for debugging/logging) + var dictionaryRepresentation: [String: Any] { + var dict: [String: Any] = [:] + + if let value = speed { dict["speed"] = value } + if let value = rpm { dict["rpm"] = value } + if let value = fuel { dict["fuel"] = value } + if let value = coolantTemp { dict["coolantTemp"] = value } + if let value = engineLoad { dict["engineLoad"] = value } + if let value = throttle { dict["throttle"] = value } + if let value = intakeTemp { dict["intakeTemp"] = value } + if let value = ambientTemp { dict["ambientTemp"] = value } + if let value = barometric { dict["barometric"] = value } + if let value = distance { dict["distance"] = value } + if let value = accelX { dict["accelX"] = value } + if let value = accelY { dict["accelY"] = value } + if let value = accelZ { dict["accelZ"] = value } + + return dict + } +} diff --git a/vBox/Models/CoreData/DrivingHistoryEntity.swift b/vBox/Models/CoreData/DrivingHistoryEntity.swift new file mode 100644 index 0000000..1e1c64d --- /dev/null +++ b/vBox/Models/CoreData/DrivingHistoryEntity.swift @@ -0,0 +1,251 @@ +// +// DrivingHistoryEntity.swift +// vBox +// +// Swift Core Data model for DrivingHistory entity +// + +import Foundation +import CoreData + +// MARK: - Driving History Entity + +@objc(DrivingHistoryEntity) +public class DrivingHistoryEntity: NSManagedObject { + + // MARK: - Core Data Properties + + @NSManaged public var trips: NSOrderedSet? +} + +// MARK: - Fetch Request + +extension DrivingHistoryEntity { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "DrivingHistory") + } +} + +// MARK: - Generated Accessors for trips + +extension DrivingHistoryEntity { + + @objc(insertObject:inTripsAtIndex:) + @NSManaged public func insertIntoTrips(_ value: TripEntity, at idx: Int) + + @objc(removeObjectFromTripsAtIndex:) + @NSManaged public func removeFromTrips(at idx: Int) + + @objc(insertTrips:atIndexes:) + @NSManaged public func insertIntoTrips(_ values: [TripEntity], at indexes: NSIndexSet) + + @objc(removeTripsAtIndexes:) + @NSManaged public func removeFromTrips(at indexes: NSIndexSet) + + @objc(replaceObjectInTripsAtIndex:withObject:) + @NSManaged public func replaceTrips(at idx: Int, with value: TripEntity) + + @objc(replaceTripsAtIndexes:withTrips:) + @NSManaged public func replaceTrips(at indexes: NSIndexSet, with values: [TripEntity]) + + @objc(addTripsObject:) + @NSManaged public func addToTrips(_ value: TripEntity) + + @objc(removeTripsObject:) + @NSManaged public func removeFromTrips(_ value: TripEntity) + + @objc(addTrips:) + @NSManaged public func addToTrips(_ values: NSOrderedSet) + + @objc(removeTrips:) + @NSManaged public func removeFromTrips(_ values: NSOrderedSet) +} + +// MARK: - Computed Properties + +extension DrivingHistoryEntity { + + /// Number of trips + var tripCount: Int { + return trips?.count ?? 0 + } + + /// Array of all trips + var tripsArray: [TripEntity] { + return trips?.array as? [TripEntity] ?? [] + } + + /// Trips sorted by start time (newest first) + var tripsSortedByDate: [TripEntity] { + return tripsArray.sorted { (trip1, trip2) -> Bool in + guard let date1 = trip1.startTime, let date2 = trip2.startTime else { + return false + } + return date1 > date2 + } + } + + /// Most recent trip + var mostRecentTrip: TripEntity? { + return tripsSortedByDate.first + } + + /// Oldest trip + var oldestTrip: TripEntity? { + return tripsSortedByDate.last + } + + /// Total distance of all trips in miles + var totalDistanceMiles: Double { + return tripsArray.reduce(0) { $0 + $1.distanceMiles } + } + + /// Total distance of all trips in kilometers + var totalDistanceKilometers: Double { + return totalDistanceMiles * 1.60934 + } + + /// Total driving time across all trips + var totalDrivingTime: TimeInterval { + return tripsArray.reduce(0) { $0 + ($1.duration ?? 0) } + } + + /// Formatted total driving time + var totalDrivingTimeString: String { + return DurationFormatter.string(from: totalDrivingTime) + } + + /// Average trip distance in miles + var averageTripDistanceMiles: Double { + guard tripCount > 0 else { return 0 } + return totalDistanceMiles / Double(tripCount) + } + + /// Average trip duration + var averageTripDuration: TimeInterval { + guard tripCount > 0 else { return 0 } + return totalDrivingTime / Double(tripCount) + } +} + +// MARK: - Trip Management + +extension DrivingHistoryEntity { + + /// Create and add a new trip + func createTrip(in context: NSManagedObjectContext) -> TripEntity { + let trip = TripEntity.create(in: context) + trip.drivingHistory = self + addToTrips(trip) + return trip + } + + /// Remove a trip + func removeTrip(_ trip: TripEntity, from context: NSManagedObjectContext) { + removeFromTrips(trip) + context.delete(trip) + } + + /// Get trips for a specific date + func trips(on date: Date) -> [TripEntity] { + let calendar = Calendar.current + return tripsArray.filter { trip in + guard let tripDate = trip.startTime else { return false } + return calendar.isDate(tripDate, inSameDayAs: date) + } + } + + /// Get trips within a date range + func trips(from startDate: Date, to endDate: Date) -> [TripEntity] { + return tripsArray.filter { trip in + guard let tripDate = trip.startTime else { return false } + return tripDate >= startDate && tripDate <= endDate + } + } + + /// Get trips grouped by date (for table view sections) + var tripsGroupedByDate: [(date: Date, trips: [TripEntity])] { + let calendar = Calendar.current + + // Group trips by day + var grouped: [Date: [TripEntity]] = [:] + + for trip in tripsArray { + guard let startTime = trip.startTime else { continue } + + let dayStart = calendar.startOfDay(for: startTime) + + if grouped[dayStart] == nil { + grouped[dayStart] = [] + } + grouped[dayStart]?.append(trip) + } + + // Convert to sorted array of tuples + return grouped + .map { (date: $0.key, trips: $0.value.sorted { ($0.startTime ?? Date()) > ($1.startTime ?? Date()) }) } + .sorted { $0.date > $1.date } + } +} + +// MARK: - Statistics + +extension DrivingHistoryEntity { + + /// Overall statistics summary + var statistics: DrivingStatistics { + return DrivingStatistics( + totalTrips: tripCount, + totalDistanceMiles: totalDistanceMiles, + totalDrivingTime: totalDrivingTime, + averageTripDistanceMiles: averageTripDistanceMiles, + averageTripDuration: averageTripDuration, + maxSpeedRecorded: tripsArray.map { $0.maximumSpeed }.max() ?? 0, + averageSpeed: tripsArray.isEmpty ? 0 : tripsArray.map { $0.averageSpeed }.reduce(0, +) / Double(tripCount) + ) + } +} + +// MARK: - Driving Statistics + +/// Summary of driving statistics +struct DrivingStatistics { + let totalTrips: Int + let totalDistanceMiles: Double + let totalDrivingTime: TimeInterval + let averageTripDistanceMiles: Double + let averageTripDuration: TimeInterval + let maxSpeedRecorded: Double + let averageSpeed: Double + + var totalDistanceKilometers: Double { + return totalDistanceMiles * 1.60934 + } + + var formattedTotalTime: String { + return DurationFormatter.humanReadable(from: totalDrivingTime) + } + + var formattedAverageDuration: String { + return DurationFormatter.humanReadable(from: averageTripDuration) + } +} + +// MARK: - Singleton Access + +extension DrivingHistoryEntity { + + /// Get or create the singleton DrivingHistory instance + static func getOrCreate(in context: NSManagedObjectContext) -> DrivingHistoryEntity { + let request = fetchRequest() + request.fetchLimit = 1 + + if let existing = try? context.fetch(request).first { + return existing + } + + let history = DrivingHistoryEntity(context: context) + return history + } +} diff --git a/vBox/Models/CoreData/GPSLocationEntity.swift b/vBox/Models/CoreData/GPSLocationEntity.swift new file mode 100644 index 0000000..f8e1bcf --- /dev/null +++ b/vBox/Models/CoreData/GPSLocationEntity.swift @@ -0,0 +1,214 @@ +// +// GPSLocationEntity.swift +// vBox +// +// Swift Core Data model for GPSLocation entity +// + +import Foundation +import CoreData +import CoreLocation + +// MARK: - GPS Location Entity + +@objc(GPSLocationEntity) +public class GPSLocationEntity: NSManagedObject { + + // MARK: - Core Data Properties + + @NSManaged public var latitude: NSNumber? + @NSManaged public var longitude: NSNumber? + @NSManaged public var speed: NSNumber? + @NSManaged public var altitude: NSNumber? + @NSManaged public var metersFromStart: NSNumber? + @NSManaged public var timestamp: Date? + @NSManaged public var tripInfo: TripEntity? + @NSManaged public var bluetoothInfo: BluetoothDataEntity? +} + +// MARK: - Fetch Request + +extension GPSLocationEntity { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "GPSLocation") + } + + /// Fetch locations for a specific trip + @nonobjc public class func fetchLocations(for trip: TripEntity) -> NSFetchRequest { + let request = fetchRequest() + request.predicate = NSPredicate(format: "tripInfo == %@", trip) + request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)] + return request + } + + /// Fetch locations within a time range + @nonobjc public class func fetchLocations(from startDate: Date, to endDate: Date) -> NSFetchRequest { + let request = fetchRequest() + request.predicate = NSPredicate( + format: "timestamp >= %@ AND timestamp <= %@", + startDate as NSDate, + endDate as NSDate + ) + request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)] + return request + } +} + +// MARK: - Computed Properties + +extension GPSLocationEntity { + + /// Coordinate as CLLocationCoordinate2D + var coordinate: CLLocationCoordinate2D? { + guard let lat = latitude?.doubleValue, + let lon = longitude?.doubleValue else { + return nil + } + return CLLocationCoordinate2D(latitude: lat, longitude: lon) + } + + /// Location as CLLocation + var clLocation: CLLocation? { + guard let lat = latitude?.doubleValue, + let lon = longitude?.doubleValue else { + return nil + } + + return CLLocation( + coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon), + altitude: altitude?.doubleValue ?? 0, + horizontalAccuracy: 10, + verticalAccuracy: 10, + course: -1, + speed: speed?.doubleValue ?? 0, + timestamp: timestamp ?? Date() + ) + } + + /// Speed in meters per second + var speedMetersPerSecond: Double { + return speed?.doubleValue ?? 0 + } + + /// Speed in kilometers per hour + var speedKmh: Double { + return speedMetersPerSecond * 3.6 + } + + /// Speed in miles per hour + var speedMph: Double { + return speedMetersPerSecond * 2.23694 + } + + /// Altitude in meters + var altitudeMeters: Double { + return altitude?.doubleValue ?? 0 + } + + /// Distance from trip start in meters + var distanceFromStart: Double { + return metersFromStart?.doubleValue ?? 0 + } + + /// Distance from trip start in kilometers + var distanceFromStartKm: Double { + return distanceFromStart / 1000 + } + + /// Distance from trip start in miles + var distanceFromStartMiles: Double { + return distanceFromStart / 1609.34 + } + + /// Whether this location has associated Bluetooth/OBD data + var hasBluetoothData: Bool { + return bluetoothInfo != nil + } +} + +// MARK: - Location Factory + +extension GPSLocationEntity { + + /// Create a new location from a CLLocation + static func create( + in context: NSManagedObjectContext, + from location: CLLocation, + metersFromStart: Double = 0 + ) -> GPSLocationEntity { + let entity = GPSLocationEntity(context: context) + entity.latitude = NSNumber(value: location.coordinate.latitude) + entity.longitude = NSNumber(value: location.coordinate.longitude) + entity.speed = NSNumber(value: max(0, location.speed)) // Speed can be -1 if unknown + entity.altitude = NSNumber(value: location.altitude) + entity.metersFromStart = NSNumber(value: metersFromStart) + entity.timestamp = location.timestamp + return entity + } + + /// Create a new location with specific coordinates + static func create( + in context: NSManagedObjectContext, + latitude: Double, + longitude: Double, + speed: Double = 0, + altitude: Double = 0, + metersFromStart: Double = 0, + timestamp: Date = Date() + ) -> GPSLocationEntity { + let entity = GPSLocationEntity(context: context) + entity.latitude = NSNumber(value: latitude) + entity.longitude = NSNumber(value: longitude) + entity.speed = NSNumber(value: speed) + entity.altitude = NSNumber(value: altitude) + entity.metersFromStart = NSNumber(value: metersFromStart) + entity.timestamp = timestamp + return entity + } +} + +// MARK: - Distance Calculations + +extension GPSLocationEntity { + + /// Calculate distance to another location + func distance(to other: GPSLocationEntity) -> Double? { + guard let selfLocation = clLocation, + let otherLocation = other.clLocation else { + return nil + } + return selfLocation.distance(from: otherLocation) + } + + /// Calculate bearing to another location (in degrees) + func bearing(to other: GPSLocationEntity) -> Double? { + guard let coord1 = coordinate, + let coord2 = other.coordinate else { + return nil + } + + let lat1 = coord1.latitude.degreesToRadians + let lat2 = coord2.latitude.degreesToRadians + let dLon = (coord2.longitude - coord1.longitude).degreesToRadians + + let y = sin(dLon) * cos(lat2) + let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon) + + let bearing = atan2(y, x).radiansToDegrees + + return (bearing + 360).truncatingRemainder(dividingBy: 360) + } +} + +// MARK: - Helper Extensions + +private extension Double { + var degreesToRadians: Double { + return self * .pi / 180 + } + + var radiansToDegrees: Double { + return self * 180 / .pi + } +} diff --git a/vBox/Models/CoreData/TripEntity.swift b/vBox/Models/CoreData/TripEntity.swift new file mode 100644 index 0000000..31fd328 --- /dev/null +++ b/vBox/Models/CoreData/TripEntity.swift @@ -0,0 +1,244 @@ +// +// TripEntity.swift +// vBox +// +// Swift Core Data model for Trip entity +// + +import Foundation +import CoreData +import CoreLocation + +// MARK: - Trip Entity + +@objc(TripEntity) +public class TripEntity: NSManagedObject { + + // MARK: - Core Data Properties + + @NSManaged public var startTime: Date? + @NSManaged public var endTime: Date? + @NSManaged public var avgSpeed: NSNumber? + @NSManaged public var maxSpeed: NSNumber? + @NSManaged public var minSpeed: NSNumber? + @NSManaged public var totalMiles: NSNumber? + @NSManaged public var tripName: String? + @NSManaged public var drivingHistory: DrivingHistoryEntity? + @NSManaged public var gpsLocations: NSOrderedSet? +} + +// MARK: - Fetch Request + +extension TripEntity { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Trip") + } + + /// Fetch all trips sorted by start time (newest first) + @nonobjc public class func fetchAllSortedByDate() -> NSFetchRequest { + let request = fetchRequest() + request.sortDescriptors = [NSSortDescriptor(key: "startTime", ascending: false)] + return request + } + + /// Fetch trips within a date range + @nonobjc public class func fetchTrips(from startDate: Date, to endDate: Date) -> NSFetchRequest { + let request = fetchRequest() + request.predicate = NSPredicate( + format: "startTime >= %@ AND startTime <= %@", + startDate as NSDate, + endDate as NSDate + ) + request.sortDescriptors = [NSSortDescriptor(key: "startTime", ascending: false)] + return request + } +} + +// MARK: - Generated Accessors for gpsLocations + +extension TripEntity { + + @objc(insertObject:inGpsLocationsAtIndex:) + @NSManaged public func insertIntoGpsLocations(_ value: GPSLocationEntity, at idx: Int) + + @objc(removeObjectFromGpsLocationsAtIndex:) + @NSManaged public func removeFromGpsLocations(at idx: Int) + + @objc(insertGpsLocations:atIndexes:) + @NSManaged public func insertIntoGpsLocations(_ values: [GPSLocationEntity], at indexes: NSIndexSet) + + @objc(removeGpsLocationsAtIndexes:) + @NSManaged public func removeFromGpsLocations(at indexes: NSIndexSet) + + @objc(replaceObjectInGpsLocationsAtIndex:withObject:) + @NSManaged public func replaceGpsLocations(at idx: Int, with value: GPSLocationEntity) + + @objc(replaceGpsLocationsAtIndexes:withGpsLocations:) + @NSManaged public func replaceGpsLocations(at indexes: NSIndexSet, with values: [GPSLocationEntity]) + + @objc(addGpsLocationsObject:) + @NSManaged public func addToGpsLocations(_ value: GPSLocationEntity) + + @objc(removeGpsLocationsObject:) + @NSManaged public func removeFromGpsLocations(_ value: GPSLocationEntity) + + @objc(addGpsLocations:) + @NSManaged public func addToGpsLocations(_ values: NSOrderedSet) + + @objc(removeGpsLocations:) + @NSManaged public func removeFromGpsLocations(_ values: NSOrderedSet) +} + +// MARK: - Computed Properties + +extension TripEntity { + + /// Duration of the trip in seconds + var duration: TimeInterval? { + guard let start = startTime, let end = endTime else { return nil } + return end.timeIntervalSince(start) + } + + /// Formatted duration string (HH:MM:SS) + var durationString: String { + guard let duration = duration else { return "00:00:00" } + return DurationFormatter.string(from: duration) + } + + /// Human-readable duration + var humanReadableDuration: String { + guard let duration = duration else { return "Unknown" } + return DurationFormatter.humanReadable(from: duration) + } + + /// Average speed as Double + var averageSpeed: Double { + return avgSpeed?.doubleValue ?? 0 + } + + /// Maximum speed as Double + var maximumSpeed: Double { + return maxSpeed?.doubleValue ?? 0 + } + + /// Minimum speed as Double + var minimumSpeed: Double { + return minSpeed?.doubleValue ?? 0 + } + + /// Total distance in miles as Double + var distanceMiles: Double { + return totalMiles?.doubleValue ?? 0 + } + + /// Total distance in kilometers + var distanceKilometers: Double { + return distanceMiles * 1.60934 + } + + /// Number of GPS locations recorded + var locationCount: Int { + return gpsLocations?.count ?? 0 + } + + /// Array of GPS locations + var locationsArray: [GPSLocationEntity] { + return gpsLocations?.array as? [GPSLocationEntity] ?? [] + } + + /// First GPS location (trip start point) + var startLocation: GPSLocationEntity? { + return gpsLocations?.firstObject as? GPSLocationEntity + } + + /// Last GPS location (trip end point) + var endLocation: GPSLocationEntity? { + return gpsLocations?.lastObject as? GPSLocationEntity + } + + /// Start coordinate + var startCoordinate: CLLocationCoordinate2D? { + guard let location = startLocation, + let lat = location.latitude?.doubleValue, + let lon = location.longitude?.doubleValue else { + return nil + } + return CLLocationCoordinate2D(latitude: lat, longitude: lon) + } + + /// End coordinate + var endCoordinate: CLLocationCoordinate2D? { + guard let location = endLocation, + let lat = location.latitude?.doubleValue, + let lon = location.longitude?.doubleValue else { + return nil + } + return CLLocationCoordinate2D(latitude: lat, longitude: lon) + } + + /// Path as array of coordinates + var pathCoordinates: [CLLocationCoordinate2D] { + return locationsArray.compactMap { location in + guard let lat = location.latitude?.doubleValue, + let lon = location.longitude?.doubleValue else { + return nil + } + return CLLocationCoordinate2D(latitude: lat, longitude: lon) + } + } +} + +// MARK: - Trip Statistics + +extension TripEntity { + + /// Calculate and update trip statistics from recorded locations + func calculateStatistics() { + let locations = locationsArray + + guard !locations.isEmpty else { return } + + // Calculate speeds + let speeds = locations.compactMap { $0.speed?.doubleValue } + + if !speeds.isEmpty { + avgSpeed = NSNumber(value: speeds.reduce(0, +) / Double(speeds.count)) + maxSpeed = NSNumber(value: speeds.max() ?? 0) + minSpeed = NSNumber(value: speeds.min() ?? 0) + } + + // Calculate total distance + if let lastLocation = locations.last, + let distance = lastLocation.metersFromStart?.doubleValue { + // Convert meters to miles + totalMiles = NSNumber(value: distance / 1609.34) + } + } +} + +// MARK: - Trip Factory + +extension TripEntity { + + /// Create a new trip in the given context + static func create(in context: NSManagedObjectContext) -> TripEntity { + let trip = TripEntity(context: context) + trip.startTime = Date() + return trip + } + + /// Create a trip with specific start/end times (for testing) + static func create( + in context: NSManagedObjectContext, + startTime: Date, + endTime: Date, + name: String? = nil + ) -> TripEntity { + let trip = TripEntity(context: context) + trip.startTime = startTime + trip.endTime = endTime + trip.tripName = name + return trip + } +} diff --git a/vBox/Models/OBDTypes.swift b/vBox/Models/OBDTypes.swift new file mode 100644 index 0000000..85ca88c --- /dev/null +++ b/vBox/Models/OBDTypes.swift @@ -0,0 +1,305 @@ +// +// OBDTypes.swift +// vBox +// +// Swift types for OBD-II protocol data +// + +import Foundation + +// MARK: - OBD-II Parameter IDs (PIDs) + +/// Standard OBD-II Parameter IDs supported by the Freematics adapter +enum OBDPID: UInt16 { + // Engine PIDs + case speed = 0x10D + case rpm = 0x10C + case engineLoad = 0x104 + case coolantTemp = 0x105 + case throttle = 0x111 + case runtime = 0x11F + case engineFuelRate = 0x159 + case engineTorquePercentage = 0x15B + + // Fuel PIDs + case fuelLevel = 0x12F + + // Environmental PIDs + case intakeTemp = 0x10F + case ambientTemp = 0x146 + case barometric = 0x133 + + // Distance + case distance = 0x131 + + // GPS PIDs (Freematics specific) + case gpsLatitude = 0xF00A + case gpsLongitude = 0xF00B + case gpsAltitude = 0x000C + case gpsSpeed = 0xF00D + case gpsHeading = 0xF00E + case gpsSatCount = 0xF00F + case gpsTime = 0xF010 + + // Accelerometer/Gyro PIDs + case accelerometer = 0xF020 + case gyroscope = 0xF021 + + /// Human-readable name for the PID + var displayName: String { + switch self { + case .speed: return "Speed" + case .rpm: return "RPM" + case .engineLoad: return "Engine Load" + case .coolantTemp: return "Coolant Temp" + case .throttle: return "Throttle" + case .runtime: return "Runtime" + case .engineFuelRate: return "Engine Fuel Rate" + case .engineTorquePercentage: return "Engine Torque Percentage" + case .fuelLevel: return "Fuel" + case .intakeTemp: return "Intake Temp" + case .ambientTemp: return "Ambient Temp" + case .barometric: return "Barometric" + case .distance: return "Distance" + case .gpsLatitude: return "GPS Latitude" + case .gpsLongitude: return "GPS Longitude" + case .gpsAltitude: return "GPS Altitude" + case .gpsSpeed: return "GPS Speed" + case .gpsHeading: return "GPS Heading" + case .gpsSatCount: return "GPS Satellites" + case .gpsTime: return "GPS Time" + case .accelerometer: return "Accelerometer" + case .gyroscope: return "Gyroscope" + } + } + + /// Maximum reasonable value for validation + var maxValidValue: Float { + switch self { + case .speed: return 1000.0 + case .rpm: return 100000.0 + case .engineLoad: return 150.0 + case .coolantTemp: return 500.0 + case .throttle: return 1000.0 + case .runtime: return .greatestFiniteMagnitude + case .engineFuelRate: return 1000.0 + case .engineTorquePercentage: return 150.0 + case .fuelLevel: return 150.0 + case .intakeTemp: return 1000.0 + case .ambientTemp: return 1000.0 + case .barometric: return 500.0 + case .distance: return 10000000.0 + default: return .greatestFiniteMagnitude + } + } +} + +// MARK: - BLE Data Packet + +/// Represents a raw BLE data packet from the Freematics OBD adapter +/// The adapter sends 12-byte packets with the following structure: +/// - time: 4 bytes (UInt32) - timestamp +/// - pid: 2 bytes (UInt16) - parameter ID +/// - flags: 1 byte (UInt8) - status flags +/// - checksum: 1 byte (UInt8) - XOR checksum +/// - value: 12 bytes (3 x Float) - up to 3 float values +struct BLEDataPacket { + static let packetSize = 24 // 4 + 2 + 1 + 1 + (4 * 4) = 24 bytes actually, but protocol uses 12 + static let legacyPacketSize = 12 + + let time: UInt32 + let pid: UInt16 + let flags: UInt8 + let checksum: UInt8 + let values: (Float, Float, Float) + + /// Initialize from raw data bytes + /// - Parameter data: Raw data from BLE characteristic (12 bytes) + /// - Returns: nil if data is invalid or checksum fails + init?(data: Data) { + guard data.count >= Self.legacyPacketSize else { return nil } + + // Parse the packet + var offset = 0 + + time = data.withUnsafeBytes { ptr in + ptr.load(fromByteOffset: offset, as: UInt32.self) + } + offset += 4 + + pid = data.withUnsafeBytes { ptr in + ptr.load(fromByteOffset: offset, as: UInt16.self) + } + offset += 2 + + flags = data[offset] + offset += 1 + + checksum = data[offset] + offset += 1 + + // For 12-byte packets, we only have one float value + // The struct in Obj-C has value[3] but only 12 bytes total + // So: 4 + 2 + 1 + 1 + 4 = 12 (only one float fits) + let value0 = data.withUnsafeBytes { ptr in + ptr.load(fromByteOffset: offset, as: Float.self) + } + values = (value0, 0, 0) + + // Validate checksum + guard Self.validateChecksum(data: data, length: Self.legacyPacketSize) else { + return nil + } + } + + /// Create a packet for testing purposes + init(time: UInt32, pid: UInt16, flags: UInt8, checksum: UInt8, values: (Float, Float, Float)) { + self.time = time + self.pid = pid + self.flags = flags + self.checksum = checksum + self.values = values + } + + /// Calculate XOR checksum for data + static func calculateChecksum(data: Data, length: Int) -> UInt8 { + var checksum: UInt8 = 0 + for i in 0.. Bool { + return calculateChecksum(data: data, length: length) == 0 + } + + /// The parsed OBD PID if it's a known type + var obdPID: OBDPID? { + return OBDPID(rawValue: pid) + } + + /// Primary value from the packet + var primaryValue: Float { + return values.0 + } +} + +// MARK: - Diagnostic Reading + +/// A validated diagnostic reading from the OBD adapter +struct DiagnosticReading { + let pid: OBDPID + let value: Float + let timestamp: Date + + init(pid: OBDPID, value: Float, timestamp: Date = Date()) { + self.pid = pid + self.value = value + self.timestamp = timestamp + } + + /// Check if the value is within valid range + var isValid: Bool { + return value >= 0 && value <= pid.maxValidValue + } + + /// Display-friendly string representation + var displayString: String { + switch pid { + case .speed: + return String(format: "%.0f km/h", value) + case .rpm: + return String(format: "%.0f RPM", value) + case .fuelLevel: + return String(format: "%.0f%%", value) + case .coolantTemp, .intakeTemp, .ambientTemp: + return String(format: "%.0f\u{00B0}C", value) + case .engineLoad, .throttle, .engineTorquePercentage: + return String(format: "%.1f%%", value) + case .barometric: + return String(format: "%.0f kPa", value) + case .distance: + return String(format: "%.1f km", value) + default: + return String(format: "%.2f", value) + } + } +} + +// MARK: - Vehicle Diagnostics Snapshot + +/// A snapshot of all current vehicle diagnostics +struct VehicleDiagnostics { + var speed: Float? + var rpm: Float? + var fuelLevel: Float? + var coolantTemp: Float? + var engineLoad: Float? + var throttle: Float? + var intakeTemp: Float? + var ambientTemp: Float? + var barometric: Float? + var distance: Float? + var runtime: Float? + var engineFuelRate: Float? + var engineTorquePercentage: Float? + + var lastUpdated: Date = Date() + + init() {} + + /// Update a diagnostic value from a reading + mutating func update(with reading: DiagnosticReading) { + guard reading.isValid else { return } + + lastUpdated = reading.timestamp + + switch reading.pid { + case .speed: speed = reading.value + case .rpm: rpm = reading.value + case .fuelLevel: fuelLevel = reading.value + case .coolantTemp: coolantTemp = reading.value + case .engineLoad: engineLoad = reading.value + case .throttle: throttle = reading.value + case .intakeTemp: intakeTemp = reading.value + case .ambientTemp: ambientTemp = reading.value + case .barometric: barometric = reading.value + case .distance: distance = reading.value + case .runtime: runtime = reading.value + case .engineFuelRate: engineFuelRate = reading.value + case .engineTorquePercentage: engineTorquePercentage = reading.value + default: break + } + } + + /// Update from a parsed BLE packet + mutating func update(with packet: BLEDataPacket) { + guard let pid = packet.obdPID else { return } + let reading = DiagnosticReading(pid: pid, value: packet.primaryValue) + update(with: reading) + } +} + +// MARK: - Accelerometer Data + +/// 3-axis accelerometer reading +struct AccelerometerReading { + let x: Float + let y: Float + let z: Float + let timestamp: Date + + init(x: Float, y: Float, z: Float, timestamp: Date = Date()) { + self.x = x + self.y = y + self.z = z + self.timestamp = timestamp + } + + /// Magnitude of the acceleration vector + var magnitude: Float { + return sqrt(x * x + y * y + z * z) + } +} diff --git a/vBox/Utilities/DateFormatting.swift b/vBox/Utilities/DateFormatting.swift new file mode 100644 index 0000000..592fa3e --- /dev/null +++ b/vBox/Utilities/DateFormatting.swift @@ -0,0 +1,191 @@ +// +// DateFormatting.swift +// vBox +// +// Swift utilities for date and time formatting +// + +import Foundation + +// MARK: - Duration Formatting + +/// Formats a time duration into a human-readable string +enum DurationFormatter { + + /// Format a time interval as HH:MM:SS + /// - Parameter interval: Time interval in seconds + /// - Returns: Formatted string like "01:23:45" + static func string(from interval: TimeInterval) -> String { + let totalSeconds = Int(interval) + let hours = totalSeconds / 3600 + let minutes = (totalSeconds / 60) % 60 + let seconds = totalSeconds % 60 + return String(format: "%02d:%02d:%02d", hours, minutes, seconds) + } + + /// Format the duration between two dates as HH:MM:SS + /// - Parameters: + /// - startDate: Start date + /// - endDate: End date + /// - Returns: Formatted string like "01:23:45" + static func string(from startDate: Date, to endDate: Date) -> String { + let interval = endDate.timeIntervalSince(startDate) + return string(from: interval) + } + + /// Format a time interval in a human-readable way + /// - Parameter interval: Time interval in seconds + /// - Returns: Human-readable string like "2 hours 15 minutes" + static func humanReadable(from interval: TimeInterval) -> String { + let totalSeconds = Int(interval) + + if totalSeconds < 60 { + return "\(totalSeconds) second\(totalSeconds == 1 ? "" : "s")" + } + + let hours = totalSeconds / 3600 + let minutes = (totalSeconds / 60) % 60 + + if hours == 0 { + return "\(minutes) minute\(minutes == 1 ? "" : "s")" + } + + if minutes == 0 { + return "\(hours) hour\(hours == 1 ? "" : "s")" + } + + return "\(hours) hour\(hours == 1 ? "" : "s") \(minutes) minute\(minutes == 1 ? "" : "s")" + } +} + +// MARK: - Date Formatting + +/// Formats dates for display in the app +enum DateDisplayFormatter { + + // MARK: - Shared Formatters (reused for performance) + + private static let fullDateTimeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "MMMM dd, yyyy (EEEE) HH:mm:ss" + return formatter + }() + + private static let shortDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + return formatter + }() + + private static let shortTimeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + private static let mediumDateTimeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + return formatter + }() + + private static let dayOfWeekFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "EEEE" + return formatter + }() + + private static let monthDayFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "MMMM d" + return formatter + }() + + // MARK: - Public Methods + + /// Format date as "March 15, 2024 (Friday) 14:30:00" + static func fullDateTime(_ date: Date) -> String { + return fullDateTimeFormatter.string(from: date) + } + + /// Format date as short date (locale-specific) + static func shortDate(_ date: Date) -> String { + return shortDateFormatter.string(from: date) + } + + /// Format time as short time (locale-specific) + static func shortTime(_ date: Date) -> String { + return shortTimeFormatter.string(from: date) + } + + /// Format as medium date with short time + static func mediumDateTime(_ date: Date) -> String { + return mediumDateTimeFormatter.string(from: date) + } + + /// Format as day of week (e.g., "Friday") + static func dayOfWeek(_ date: Date) -> String { + return dayOfWeekFormatter.string(from: date) + } + + /// Format as month and day (e.g., "March 15") + static func monthDay(_ date: Date) -> String { + return monthDayFormatter.string(from: date) + } + + /// Relative date description (Today, Yesterday, or formatted date) + static func relativeDate(_ date: Date) -> String { + let calendar = Calendar.current + + if calendar.isDateInToday(date) { + return "Today" + } else if calendar.isDateInYesterday(date) { + return "Yesterday" + } else if calendar.isDate(date, equalTo: Date(), toGranularity: .weekOfYear) { + return dayOfWeek(date) + } else { + return shortDate(date) + } + } + + /// Trip timestamp format: relative date + time + static func tripTimestamp(_ date: Date) -> String { + return "\(relativeDate(date)) at \(shortTime(date))" + } +} + +// MARK: - Date Extensions + +extension Date { + /// Duration since this date in HH:MM:SS format + var durationSinceNow: String { + return DurationFormatter.string(from: self, to: Date()) + } + + /// Full date time string + var fullDateTimeString: String { + return DateDisplayFormatter.fullDateTime(self) + } + + /// Relative date string + var relativeDateString: String { + return DateDisplayFormatter.relativeDate(self) + } +} + +// MARK: - TimeInterval Extensions + +extension TimeInterval { + /// Duration in HH:MM:SS format + var durationString: String { + return DurationFormatter.string(from: self) + } + + /// Human readable duration + var humanReadableDuration: String { + return DurationFormatter.humanReadable(from: self) + } +} diff --git a/vBox/ViewControllers/BluetoothTableViewController.swift b/vBox/ViewControllers/BluetoothTableViewController.swift new file mode 100644 index 0000000..e9e7ec9 --- /dev/null +++ b/vBox/ViewControllers/BluetoothTableViewController.swift @@ -0,0 +1,255 @@ +// +// BluetoothTableViewController.swift +// vBox +// +// Swift implementation of OBD diagnostics display +// + +import UIKit +import Combine + +// MARK: - Diagnostic Item + +private struct DiagnosticItem: Comparable { + let key: String + let displayValue: String + + static func < (lhs: DiagnosticItem, rhs: DiagnosticItem) -> Bool { + return lhs.key < rhs.key + } +} + +// MARK: - Bluetooth Table View Controller + +final class BluetoothTableViewControllerSwift: UIViewController { + + // MARK: - IBOutlets + + @IBOutlet private weak var tableView: UITableView! + @IBOutlet private weak var startBarButton: UIBarButtonItem! + @IBOutlet private weak var pauseBarButton: UIBarButtonItem! + + // MARK: - Properties + + private let bleManager = BLEManagerSwift() + private var cancellables = Set() + private var diagnosticItems: [DiagnosticItem] = [] + private var spinner: UIActivityIndicatorView! + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + setupTableView() + setupBindings() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + bleManager.disconnect() + } + + // MARK: - Setup + + private func setupUI() { + pauseBarButton.isEnabled = false + + // Setup spinner in navigation bar + spinner = UIActivityIndicatorView(style: .medium) + navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner) + } + + private func setupTableView() { + tableView.dataSource = self + tableView.delegate = self + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DiagnosticCell") + } + + private func setupBindings() { + // Subscribe to BLE state changes + bleManager.statePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + self?.handleStateChange(state) + } + .store(in: &cancellables) + + // Subscribe to connection status + bleManager.connectionPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] isConnected in + self?.handleConnectionChange(isConnected) + } + .store(in: &cancellables) + + // Subscribe to diagnostics + bleManager.diagnosticsPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] diagnostics in + self?.updateDiagnostics(diagnostics) + } + .store(in: &cancellables) + + // Subscribe to scanning status + bleManager.$isScanning + .receive(on: DispatchQueue.main) + .sink { [weak self] isScanning in + self?.handleScanningChange(isScanning) + } + .store(in: &cancellables) + } + + // MARK: - State Handling + + private func handleStateChange(_ state: BLEState) { + spinner.stopAnimating() + + let title: String + let enableStart: Bool + + switch state { + case .on: + title = "Bluetooth On" + enableStart = true + case .off: + title = "Bluetooth Off" + enableStart = false + case .resetting: + title = "Bluetooth Resetting" + enableStart = false + case .unauthorized: + title = "Bluetooth Unauthorized" + enableStart = false + case .unknown: + title = "Bluetooth Unknown" + enableStart = false + case .unsupported: + title = "Bluetooth Unsupported" + enableStart = false + } + + navigationItem.title = title + startBarButton.isEnabled = enableStart + pauseBarButton.isEnabled = false + } + + private func handleConnectionChange(_ isConnected: Bool) { + spinner.stopAnimating() + + if isConnected { + navigationItem.title = "Connected" + } else { + navigationItem.title = "Disconnected" + pauseBarButton.isEnabled = false + startBarButton.isEnabled = true + } + } + + private func handleScanningChange(_ isScanning: Bool) { + if isScanning { + spinner.startAnimating() + navigationItem.title = "Scanning..." + } else { + spinner.stopAnimating() + if bleManager.isConnected { + navigationItem.title = "Connected" + } else { + navigationItem.title = "Not Connected" + } + } + } + + // MARK: - Diagnostics Update + + private func updateDiagnostics(_ diagnostics: VehicleDiagnostics) { + var items: [DiagnosticItem] = [] + + if let speed = diagnostics.speed { + items.append(DiagnosticItem(key: "Speed", displayValue: "\(Int(speed)) km/h")) + } + if let rpm = diagnostics.rpm { + items.append(DiagnosticItem(key: "RPM", displayValue: "\(Int(rpm))")) + } + if let fuel = diagnostics.fuelLevel { + items.append(DiagnosticItem(key: "Fuel Level", displayValue: "\(Int(fuel))%")) + } + if let coolant = diagnostics.coolantTemp { + items.append(DiagnosticItem(key: "Coolant Temp", displayValue: "\(Int(coolant))\u{00B0}C")) + } + if let intake = diagnostics.intakeTemp { + items.append(DiagnosticItem(key: "Intake Temp", displayValue: "\(Int(intake))\u{00B0}C")) + } + if let ambient = diagnostics.ambientTemp { + items.append(DiagnosticItem(key: "Ambient Temp", displayValue: "\(Int(ambient))\u{00B0}C")) + } + if let load = diagnostics.engineLoad { + items.append(DiagnosticItem(key: "Engine Load", displayValue: "\(Int(load))%")) + } + if let throttle = diagnostics.throttlePosition { + items.append(DiagnosticItem(key: "Throttle", displayValue: "\(Int(throttle))%")) + } + if let barometric = diagnostics.barometricPressure { + items.append(DiagnosticItem(key: "Barometric", displayValue: "\(Int(barometric)) kPa")) + } + if let distance = diagnostics.distanceSinceCodesCleared { + items.append(DiagnosticItem(key: "Distance", displayValue: "\(Int(distance)) km")) + } + if let accel = diagnostics.accelerometer { + items.append(DiagnosticItem( + key: "Accelerometer", + displayValue: "(\(String(format: "%.2f", accel.x)), \(String(format: "%.2f", accel.y)), \(String(format: "%.2f", accel.z)))" + )) + } + + diagnosticItems = items.sorted() + tableView.reloadData() + } + + // MARK: - Actions + + @IBAction private func startButtonPressed(_ sender: Any) { + if bleManager.isConnected { + bleManager.setNotifyValue(true) + } else { + _ = bleManager.scan(for: .obdAdapter) + } + + startBarButton.isEnabled = false + pauseBarButton.isEnabled = true + } + + @IBAction private func pauseBarButtonPressed(_ sender: Any) { + if bleManager.isConnected { + bleManager.setNotifyValue(false) + } else { + bleManager.stopScanning() + } + + startBarButton.isEnabled = true + pauseBarButton.isEnabled = false + } +} + +// MARK: - UITableViewDataSource + +extension BluetoothTableViewControllerSwift: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return diagnosticItems.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "DiagnosticCell", for: indexPath) + let item = diagnosticItems[indexPath.row] + cell.textLabel?.text = "\(item.key) - \(item.displayValue)" + return cell + } +} + +// MARK: - UITableViewDelegate + +extension BluetoothTableViewControllerSwift: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } +} diff --git a/vBox/ViewControllers/DebugBluetoothViewController.swift b/vBox/ViewControllers/DebugBluetoothViewController.swift new file mode 100644 index 0000000..7196c85 --- /dev/null +++ b/vBox/ViewControllers/DebugBluetoothViewController.swift @@ -0,0 +1,154 @@ +// +// DebugBluetoothViewController.swift +// vBox +// +// Swift implementation of BLE debug console +// + +import UIKit +import Combine + +// MARK: - Debug Bluetooth View Controller + +final class DebugBluetoothViewControllerSwift: UIViewController { + + // MARK: - IBOutlets + + @IBOutlet private weak var textView: UITextView! + + // MARK: - Properties + + private let bleManager = BLEManagerSwift() + private var cancellables = Set() + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + setupBindings() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + bleManager.disconnect() + } + + // MARK: - Setup + + private func setupUI() { + textView.isEditable = false + textView.font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) + textView.text = "BLE Debug Console\n" + String(repeating: "-", count: 40) + "\n" + } + + private func setupBindings() { + // Subscribe to BLE state changes + bleManager.statePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + self?.handleStateChange(state) + } + .store(in: &cancellables) + + // Subscribe to diagnostics updates + bleManager.diagnosticsPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] diagnostics in + self?.logDiagnostics(diagnostics) + } + .store(in: &cancellables) + + // Subscribe to connection status + bleManager.connectionPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] isConnected in + self?.log(isConnected ? "Connected to device" : "Disconnected") + } + .store(in: &cancellables) + } + + // MARK: - State Handling + + private func handleStateChange(_ state: BLEState) { + log("State changed to: \(state.description)") + + if state == .on { + log("Starting scan for OBD adapter...") + _ = bleManager.scan(for: .obdAdapter) + } + } + + // MARK: - Logging + + private func log(_ message: String) { + let timestamp = DateDisplayFormatter.timeString(from: Date()) + let logLine = "[\(timestamp)] \(message)\n" + + textView.text.append(logLine) + scrollToBottom() + } + + private func logDiagnostics(_ diagnostics: VehicleDiagnostics) { + var updates: [String] = [] + + if let speed = diagnostics.speed { + updates.append("Speed: \(Int(speed)) km/h") + } + if let rpm = diagnostics.rpm { + updates.append("RPM: \(Int(rpm))") + } + if let fuel = diagnostics.fuelLevel { + updates.append("Fuel: \(Int(fuel))%") + } + if let coolant = diagnostics.coolantTemp { + updates.append("Coolant: \(Int(coolant))\u{00B0}C") + } + if let load = diagnostics.engineLoad { + updates.append("Load: \(Int(load))%") + } + if let throttle = diagnostics.throttlePosition { + updates.append("Throttle: \(Int(throttle))%") + } + + if !updates.isEmpty { + log(updates.joined(separator: " | ")) + } + } + + private func scrollToBottom() { + guard !textView.text.isEmpty else { return } + let range = NSRange(location: textView.text.count - 1, length: 1) + textView.scrollRangeToVisible(range) + } + + // MARK: - Actions + + @IBAction private func clearLogTapped(_ sender: Any) { + textView.text = "BLE Debug Console\n" + String(repeating: "-", count: 40) + "\n" + } + + @IBAction private func reconnectTapped(_ sender: Any) { + log("Reconnecting...") + bleManager.disconnect() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + _ = self?.bleManager.scan(for: .obdAdapter) + } + } +} + +// MARK: - BLE State Description Extension + +extension BLEState { + var description: String { + switch self { + case .unknown: return "Unknown" + case .resetting: return "Resetting" + case .unsupported: return "Unsupported" + case .unauthorized: return "Unauthorized" + case .off: return "Off" + case .on: return "On" + } + } +} diff --git a/vBox/ViewControllers/DrivingHistoryViewController.swift b/vBox/ViewControllers/DrivingHistoryViewController.swift new file mode 100644 index 0000000..fefc227 --- /dev/null +++ b/vBox/ViewControllers/DrivingHistoryViewController.swift @@ -0,0 +1,205 @@ +// +// DrivingHistoryViewController.swift +// vBox +// +// Swift implementation of trip list view +// + +import UIKit +import CoreData + +// MARK: - Driving History View Controller + +final class DrivingHistoryViewControllerSwift: UIViewController { + + // MARK: - IBOutlets + + @IBOutlet private weak var tableView: UITableView! + + // MARK: - Properties + + private var tripsGroupedByDate: [(date: Date, trips: [Trip])] = [] + private lazy var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + return formatter + }() + private lazy var timeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .medium + formatter.timeZone = .current + return formatter + }() + + private var appDelegate: AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupTableView() + loadTrips() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if let selectedIndexPath = tableView.indexPathForSelectedRow { + tableView.deselectRow(at: selectedIndexPath, animated: animated) + } + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + // MARK: - Setup + + private func setupTableView() { + tableView.dataSource = self + tableView.delegate = self + } + + private func loadTrips() { + guard let trips = appDelegate.drivingHistory.trips else { + tripsGroupedByDate = [] + return + } + + // Group trips by date + var grouped: [Date: [Trip]] = [:] + let calendar = Calendar.current + + for case let trip as Trip in trips.reversed() { + guard let startTime = trip.startTime else { continue } + + let dayStart = calendar.startOfDay(for: startTime) + if grouped[dayStart] == nil { + grouped[dayStart] = [] + } + grouped[dayStart]?.append(trip) + } + + // Sort by date (newest first) and convert to array of tuples + tripsGroupedByDate = grouped + .map { (date: $0.key, trips: $0.value) } + .sorted { $0.date > $1.date } + + tableView.reloadData() + } + + // MARK: - Navigation + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "tripDetailSegue", + let destination = segue.destination as? TripDetailViewController, + let indexPath = sender as? IndexPath { + destination.trip = trip(at: indexPath) + } + } + + // MARK: - Helper Methods + + private func trip(at indexPath: IndexPath) -> Trip { + return tripsGroupedByDate[indexPath.section].trips[indexPath.row] + } + + private func totalMiles(in section: Int) -> Double { + return tripsGroupedByDate[section].trips.reduce(0) { $0 + ($1.totalMiles?.doubleValue ?? 0) } + } + + private func headerTitle(for section: Int) -> String { + let date = tripsGroupedByDate[section].date + let dateString = dateFormatter.string(from: date) + let totalMiles = totalMiles(in: section) + return "\(dateString) (\(String(format: "%.2f", totalMiles)) mi)" + } + + private func deleteTrip(at indexPath: IndexPath) { + let tripToDelete = trip(at: indexPath) + let context = appDelegate.managedObjectContext + let tripsInSection = tripsGroupedByDate[indexPath.section].trips.count + + context.delete(tripToDelete) + appDelegate.saveContext() + appDelegate.forgetDrivingHistory() + + // Reload data structure + loadTrips() + + // Animate deletion + tableView.beginUpdates() + if tripsInSection == 1 { + // Last trip in section - delete entire section + tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic) + } else { + // Delete just the row + tableView.deleteRows(at: [indexPath], with: .automatic) + } + tableView.endUpdates() + } +} + +// MARK: - UITableViewDataSource + +extension DrivingHistoryViewControllerSwift: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return tripsGroupedByDate.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return tripsGroupedByDate[section].trips.count + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return headerTitle(for: section) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "trip") ?? UITableViewCell(style: .subtitle, reuseIdentifier: "trip") + + let trip = trip(at: indexPath) + + // Format time range + let startTimeText = trip.startTime.map { timeFormatter.string(from: $0) } ?? "--:--" + let endTimeText = trip.endTime.map { timeFormatter.string(from: $0) } ?? "--:--" + cell.textLabel?.text = "\(startTimeText) - \(endTimeText)" + + // Format trip stats + let avgSpeed = trip.avgSpeed?.doubleValue ?? 0 + let maxSpeed = trip.maxSpeed?.doubleValue ?? 0 + let totalMiles = trip.totalMiles?.doubleValue ?? 0 + cell.detailTextLabel?.text = String(format: "avg: %.2f mph - max: %.2f mph - (%.2f mi)", avgSpeed, maxSpeed, totalMiles) + + return cell + } + + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + deleteTrip(at: indexPath) + } + } +} + +// MARK: - UITableViewDelegate + +extension DrivingHistoryViewControllerSwift: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + performSegue(withIdentifier: "tripDetailSegue", sender: indexPath) + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 30 + } + + func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + guard let header = view as? UITableViewHeaderFooterView else { return } + header.textLabel?.textColor = .white + header.tintColor = MyStyleKit.myOrange() + header.textLabel?.font = UIFont.boldSystemFont(ofSize: UIFont.systemFontSize + 3) + header.textLabel?.textAlignment = .center + } +} diff --git a/vBox/ViewControllers/GoogleMapsViewController.swift b/vBox/ViewControllers/GoogleMapsViewController.swift new file mode 100644 index 0000000..15d69c2 --- /dev/null +++ b/vBox/ViewControllers/GoogleMapsViewController.swift @@ -0,0 +1,526 @@ +// +// GoogleMapsViewController.swift +// vBox +// +// Swift implementation of main driving/recording view +// + +import UIKit +import CoreLocation +import GoogleMaps +import Combine + +// MARK: - Delegate Protocol + +@objc protocol GoogleMapsViewControllerSwiftDelegate: AnyObject { + func didTapStopRecordingButton() +} + +// MARK: - Google Maps View Controller + +final class GoogleMapsViewControllerSwift: UIViewController { + + // MARK: - IBOutlets + + @IBOutlet private weak var MapView: GMSMapView! + @IBOutlet private weak var stopRecordingButton: UIButton! + @IBOutlet private weak var speedOrDistanceLabel: UILabel! + @IBOutlet private weak var collectionView: UICollectionView! + @IBOutlet private weak var bleButton: UIButton! + @IBOutlet private weak var bluetoothRequiredLabel: UILabel! + @IBOutlet private weak var infoView: UIView! + @IBOutlet private weak var mapViewToInfoViewConstraint: NSLayoutConstraint! + + // MARK: - Properties + + @objc weak var delegate: GoogleMapsViewControllerSwiftDelegate? + + private var locationManager: CLLocationManager! + private var bleManager: BLEManagerSwift? + private var cancellables = Set() + + private var completePath: GMSMutablePath! + private var polyline: GMSPolyline! + private var prevLocation: CLLocation? + private var isFollowingMe = true + private var showSpeed = true + private var isBLEOn = false + + private var currentTrip: Trip! + private var sumSpeed: Double = 0 + private var maxSpeed: Double = 0 + private var minSpeed: Double = .greatestFiniteMagnitude + + private var infoViewFrame: CGRect = .zero + private var mapViewFrame: CGRect = .zero + private var infoViewHiddenOffScreen: CGRect = .zero + + private var bluetoothDiagnostics: [String: NSNumber] = [:] + + private var appDelegate: AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } + + private var context: NSManagedObjectContext { + return appDelegate.managedObjectContext + } + + private let pathStyles: [GMSStrokeStyle] = [ + .solidColor(UIColor(red: 0.2666666667, green: 0.4666666667, blue: 0.6, alpha: 1)), + .solidColor(UIColor(red: 0.6666666667, green: 0.8, blue: 0.8, alpha: 1)) + ] + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Create new trip + currentTrip = NSEntityDescription.insertNewObject(forEntityName: "Trip", into: context) as? Trip + currentTrip.startTime = Date() + + completePath = GMSMutablePath() + + setupUI() + setupLocationManager() + setupGoogleMaps() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + infoViewFrame = infoView.frame + mapViewFrame = MapView.frame + infoViewHiddenOffScreen = infoView.frame + infoViewHiddenOffScreen.origin.y = UIScreen.main.bounds.height + + updateViewsBasedOnBLEState(isBLEOn, animate: false) + } + + override func viewWillDisappear(_ animated: Bool) { + MapView.clear() + locationManager.stopUpdatingLocation() + + cleanUpBluetoothManager() + + // Delete trip if no locations recorded + if currentTrip.gpsLocations?.count == 0 { + context.delete(currentTrip) + appDelegate.saveContext() + super.viewWillDisappear(animated) + return + } + + // Save trip statistics + currentTrip.endTime = Date() + let count = currentTrip.gpsLocations?.count ?? 0 + let avgSpeed = count > 0 ? sumSpeed / Double(count) : 0 + currentTrip.avgSpeed = NSNumber(value: avgSpeed) + currentTrip.maxSpeed = NSNumber(value: maxSpeed) + currentTrip.minSpeed = NSNumber(value: minSpeed) + currentTrip.totalMiles = NSNumber(value: GMSGeometryLength(completePath) * 0.000621371) + + appDelegate.drivingHistory.addToTrips(currentTrip) + appDelegate.saveContext() + + super.viewWillDisappear(animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .default + } + + // MARK: - Setup + + private func setupUI() { + // Speed label tap gesture + speedOrDistanceLabel.isUserInteractionEnabled = true + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(speedLabelTapped)) + speedOrDistanceLabel.addGestureRecognizer(tapGesture) + + // Button styling + bleButton.layer.masksToBounds = true + bleButton.layer.cornerRadius = 5.0 + + stopRecordingButton.layer.masksToBounds = true + stopRecordingButton.layer.cornerRadius = 5.0 + let buttonColor = UIColor(red: 0, green: 122.0 / 255.0, blue: 1, alpha: 1) + stopRecordingButton.setBackgroundImage(MyStyleKit.image(ofVBoxButtonWithButtonColor: buttonColor), for: .normal) + + speedOrDistanceLabel.layer.masksToBounds = true + speedOrDistanceLabel.layer.cornerRadius = 5.0 + } + + private func setupLocationManager() { + locationManager = CLLocationManager() + locationManager.delegate = self + locationManager.distanceFilter = kCLDistanceFilterNone + locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation + locationManager.activityType = .automotiveNavigation + locationManager.pausesLocationUpdatesAutomatically = true + + locationManager.requestWhenInUseAuthorization() + locationManager.requestAlwaysAuthorization() + locationManager.startUpdatingLocation() + } + + private func setupGoogleMaps() { + let camera = GMSCameraPosition(latitude: 39.490179, longitude: -98.081992, zoom: 3) + + MapView.padding = UIEdgeInsets(top: 85, left: 0, bottom: 0, right: 5) + MapView.camera = camera + MapView.isMyLocationEnabled = true + MapView.settings.myLocationButton = true + MapView.settings.compassButton = true + MapView.delegate = self + + polyline = GMSPolyline(path: completePath) + polyline.strokeColor = .gray + polyline.strokeWidth = 5.0 + polyline.geodesic = true + polyline.map = MapView + } + + private func setupBluetoothManager() { + bleManager = BLEManagerSwift() + + // Subscribe to state changes + bleManager?.statePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + self?.handleBLEStateChange(state) + } + .store(in: &cancellables) + + // Subscribe to connection changes + bleManager?.connectionPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] isConnected in + if isConnected { + SVProgressHUD.showSuccess(withStatus: "Connected") + } else { + SVProgressHUD.showError(withStatus: "Disconnected") + } + } + .store(in: &cancellables) + + // Subscribe to diagnostics + bleManager?.diagnosticsPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] diagnostics in + self?.updateBluetoothDiagnostics(diagnostics) + } + .store(in: &cancellables) + + // Subscribe to scanning status + bleManager?.$isScanning + .receive(on: DispatchQueue.main) + .sink { isScanning in + if isScanning { + SVProgressHUD.show(withStatus: "Scanning...") + } + } + .store(in: &cancellables) + } + + // MARK: - BLE State Handling + + private func handleBLEStateChange(_ state: BLEState) { + switch state { + case .on: + bluetoothRequiredLabel.isHidden = true + let peripheralType: PeripheralType = UserDefaults.standard.bool(forKey: "connectToOBD") ? .obdAdapter : .beagleBone + _ = bleManager?.scan(for: peripheralType) + case .off: + SVProgressHUD.showError(withStatus: "Bluetooth Off") + bluetoothRequiredLabel.isHidden = false + case .resetting: + SVProgressHUD.showError(withStatus: "Bluetooth Resetting") + bluetoothRequiredLabel.isHidden = false + case .unauthorized: + SVProgressHUD.showError(withStatus: "Bluetooth Unauthorized") + bluetoothRequiredLabel.isHidden = false + case .unknown: + SVProgressHUD.showError(withStatus: "Bluetooth State Unknown") + bluetoothRequiredLabel.isHidden = false + case .unsupported: + SVProgressHUD.showError(withStatus: "Bluetooth Unsupported") + bluetoothRequiredLabel.isHidden = false + } + } + + private func updateBluetoothDiagnostics(_ diagnostics: VehicleDiagnostics) { + if let speed = diagnostics.speed { + bluetoothDiagnostics["Speed"] = NSNumber(value: speed) + } + if let rpm = diagnostics.rpm { + bluetoothDiagnostics["RPM"] = NSNumber(value: rpm) + } + if let fuel = diagnostics.fuelLevel { + bluetoothDiagnostics["Fuel"] = NSNumber(value: fuel) + } + if let coolant = diagnostics.coolantTemp { + bluetoothDiagnostics["Coolant Temp"] = NSNumber(value: coolant) + } + if let intake = diagnostics.intakeTemp { + bluetoothDiagnostics["Intake Temp"] = NSNumber(value: intake) + } + if let ambient = diagnostics.ambientTemp { + bluetoothDiagnostics["Ambient Temp"] = NSNumber(value: ambient) + } + if let load = diagnostics.engineLoad { + bluetoothDiagnostics["Engine Load"] = NSNumber(value: load) + } + if let throttle = diagnostics.throttlePosition { + bluetoothDiagnostics["Throttle"] = NSNumber(value: throttle) + } + if let barometric = diagnostics.barometricPressure { + bluetoothDiagnostics["Barometric"] = NSNumber(value: barometric) + } + if let distance = diagnostics.distanceSinceCodesCleared { + bluetoothDiagnostics["Distance"] = NSNumber(value: distance) + } + + collectionView.reloadData() + } + + // MARK: - Actions + + @IBAction private func stopRecordingButtonTapped(_ sender: Any) { + navigationController?.popViewController(animated: true) + delegate?.didTapStopRecordingButton() + } + + @IBAction private func bleButtonTapped(_ sender: UIButton) { + isBLEOn.toggle() + + if isBLEOn { + sender.setImage(UIImage(named: "bleOn"), for: .normal) + setupBluetoothManager() + } else { + sender.setImage(UIImage(named: "bleOff"), for: .normal) + cleanUpBluetoothManager() + } + + updateViewsBasedOnBLEState(isBLEOn, animate: true) + } + + @objc private func speedLabelTapped() { + showSpeed.toggle() + updateSpeedLabel(with: prevLocation) + } + + // MARK: - UI Updates + + private func updateSpeedLabel(with location: CLLocation?) { + guard let location = location else { return } + + if showSpeed { + speedOrDistanceLabel.text = String(format: " %.2f mph", location.speed * 2.23694) + } else { + speedOrDistanceLabel.text = String(format: " %.2f mi", GMSGeometryLength(completePath) * 0.000621371) + } + } + + private func updateViewsBasedOnBLEState(_ state: Bool, animate: Bool) { + let animations: () -> Void = { + if state { + self.infoView.frame = self.infoViewFrame + self.MapView.frame = self.mapViewFrame + self.infoView.isHidden = false + } else { + self.infoView.frame = self.infoViewHiddenOffScreen + self.MapView.frame = UIScreen.main.bounds + } + } + + let completion: (Bool) -> Void = { _ in + if !state { + self.infoView.isHidden = true + } + self.bleButton.isEnabled = true + } + + if animate { + bleButton.isEnabled = false + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: animations, completion: completion) + } else { + animations() + if !state { + infoView.isHidden = true + } + } + } + + // MARK: - Core Data + + private func logLocation(_ location: CLLocation, persistent: Bool) { + let lat = location.coordinate.latitude + let lon = location.coordinate.longitude + let speedMPH = max(0, location.speed * 2.236936284) + let altitude = location.altitude * 3.28084 + + guard persistent else { return } + + let newLocation = NSEntityDescription.insertNewObject(forEntityName: "GPSLocation", into: context) as! GPSLocation + newLocation.latitude = NSNumber(value: lat) + newLocation.longitude = NSNumber(value: lon) + newLocation.speed = NSNumber(value: speedMPH) + newLocation.metersFromStart = NSNumber(value: GMSGeometryLength(completePath)) + newLocation.timestamp = location.timestamp + newLocation.tripInfo = currentTrip + newLocation.altitude = NSNumber(value: altitude) + + // Add Bluetooth data if connected + if let bleManager = bleManager, bleManager.isConnected { + let bleData = NSEntityDescription.insertNewObject(forEntityName: "BluetoothData", into: context) as! BluetoothData + + // Convert speed from km/h to mph + if let speedKmh = bluetoothDiagnostics["Speed"] { + bleData.speed = NSNumber(value: speedKmh.doubleValue * 0.621371) + } + + bleData.ambientTemp = bluetoothDiagnostics["Ambient Temp"] + bleData.barometric = bluetoothDiagnostics["Barometric"] + bleData.rpm = bluetoothDiagnostics["RPM"] + bleData.intakeTemp = bluetoothDiagnostics["Intake Temp"] + bleData.fuel = bluetoothDiagnostics["Fuel"] + bleData.engineLoad = bluetoothDiagnostics["Engine Load"] + bleData.distance = bluetoothDiagnostics["Distance"] + bleData.coolantTemp = bluetoothDiagnostics["Coolant Temp"] + bleData.throttle = bluetoothDiagnostics["Throttle"] + + newLocation.bluetoothInfo = bleData + } + + appDelegate.saveContext() + } + + // MARK: - Cleanup + + private func cleanUpBluetoothManager() { + if let bleManager = bleManager { + if bleManager.isConnected { + bleManager.disconnect() + } + bleManager.stopScanning() + } + + cancellables.removeAll() + bleManager = nil + + SVProgressHUD.dismiss() + } +} + +// MARK: - CLLocationManagerDelegate + +extension GoogleMapsViewControllerSwift: CLLocationManagerDelegate { + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let newestLocation = locations.last else { return } + + // Initial camera positioning + if isFollowingMe && prevLocation == nil && newestLocation.horizontalAccuracy < 70 { + MapView.animate(toLocation: newestLocation.coordinate) + if MapView.camera.zoom < 10 { + MapView.animate(toZoom: 15) + } + } + + for location in locations { + // Skip inaccurate readings + guard location.horizontalAccuracy <= 30 else { continue } + + let speedMPH = max(0, newestLocation.speed * 2.236936284) + + // Update statistics + if speedMPH < minSpeed { + minSpeed = speedMPH + } + if speedMPH > maxSpeed { + maxSpeed = speedMPH + } + sumSpeed += speedMPH + + updateSpeedLabel(with: newestLocation) + logLocation(newestLocation, persistent: true) + + completePath.add(newestLocation.coordinate) + polyline.path = completePath + } + + // Update polyline styling + let tolerance = pow(10.0, (-0.301 * Double(MapView.camera.zoom)) + 9.0731) / 2500.0 + let lengths = [NSNumber(value: tolerance), NSNumber(value: tolerance * 1.5)] + polyline.spans = GMSStyleSpans(polyline.path!, pathStyles, lengths, .geodesic) + + prevLocation = newestLocation + + if isFollowingMe { + MapView.animate(toLocation: newestLocation.coordinate) + } + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + if (error as NSError).domain == kCLErrorDomain.description { + return // Ignore CoreLocation domain errors + } + + let alert = UIAlertController( + title: error.localizedDescription, + message: "There was an error retrieving your location", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + present(alert, animated: true) + } +} + +// MARK: - GMSMapViewDelegate + +extension GoogleMapsViewControllerSwift: GMSMapViewDelegate { + func mapView(_ mapView: GMSMapView, willMove gesture: Bool) { + if gesture { + isFollowingMe = false + } + } + + func didTapMyLocationButton(for mapView: GMSMapView) -> Bool { + isFollowingMe = true + return false + } +} + +// MARK: - UICollectionViewDataSource + +extension GoogleMapsViewControllerSwift: UICollectionViewDataSource { + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return bluetoothDiagnostics.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let keys = bluetoothDiagnostics.keys.sorted() + let key = keys[indexPath.row] + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) + + if let keyLabel = cell.viewWithTag(1) as? UILabel { + keyLabel.text = key + } + if let valLabel = cell.viewWithTag(2) as? UILabel { + valLabel.text = "\(bluetoothDiagnostics[key] ?? 0)" + } + + return cell + } +} + +// MARK: - UICollectionViewDelegate + +extension GoogleMapsViewControllerSwift: UICollectionViewDelegate { + // Implement delegate methods if needed +} diff --git a/vBox/ViewControllers/MainScreenViewController.swift b/vBox/ViewControllers/MainScreenViewController.swift new file mode 100644 index 0000000..40d91b0 --- /dev/null +++ b/vBox/ViewControllers/MainScreenViewController.swift @@ -0,0 +1,90 @@ +// +// MainScreenViewController.swift +// vBox +// +// Swift implementation of main screen navigation +// + +import UIKit + +// MARK: - Main Screen View Controller + +final class MainScreenViewControllerSwift: UIViewController { + + // MARK: - IBOutlets + + @IBOutlet private weak var startDriveButton: UIButton! + @IBOutlet private weak var drivingHistoryButton: UIButton! + @IBOutlet private weak var debugBluetoothButton: UIButton! + @IBOutlet private weak var bluetoothTableButton: UIButton! + + // MARK: - Properties + + private var shouldShowNavigationBar = true + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupButtons() + configureDebugMode() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if shouldShowNavigationBar { + navigationController?.setNavigationBarHidden(false, animated: true) + } + shouldShowNavigationBar = true + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + // MARK: - Setup + + private func setupButtons() { + // Configure button styles using MyStyleKit + let blueImage = MyStyleKit.image(ofVBoxButtonWithButtonColor: MyStyleKit.gamebookersBlueColor()) + let orangeImage = MyStyleKit.image(ofVBoxButtonWithButtonColor: MyStyleKit.myOrange()) + let debugImage = MyStyleKit.image(ofVBoxButtonWithButtonColor: MyStyleKit.route66Color()) + + startDriveButton.setBackgroundImage(blueImage, for: .normal) + drivingHistoryButton.setBackgroundImage(orangeImage, for: .normal) + debugBluetoothButton.setBackgroundImage(debugImage, for: .normal) + bluetoothTableButton.setBackgroundImage(debugImage, for: .normal) + } + + private func configureDebugMode() { + let isDebugMode = UserDefaults.standard.bool(forKey: "debugMode") + debugBluetoothButton.isHidden = !isDebugMode + bluetoothTableButton.isHidden = !isDebugMode + } + + // MARK: - Navigation + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "googleMapsSegue" { + shouldShowNavigationBar = false + + if let googleMapsVC = segue.destination as? GoogleMapsViewController { + googleMapsVC.delegate = self + } + } + } +} + +// MARK: - GoogleMapsViewControllerDelegate + +extension MainScreenViewControllerSwift: GoogleMapsViewControllerDelegate { + func didTapStopRecordingButton() { + dismiss(animated: true, completion: nil) + } +} diff --git a/vBox/ViewControllers/TripDetailViewController.swift b/vBox/ViewControllers/TripDetailViewController.swift new file mode 100644 index 0000000..0944813 --- /dev/null +++ b/vBox/ViewControllers/TripDetailViewController.swift @@ -0,0 +1,455 @@ +// +// TripDetailViewController.swift +// vBox +// +// Swift implementation of trip playback view +// + +import UIKit +import GoogleMaps +import MessageUI + +// MARK: - Trip Detail View Controller + +final class TripDetailViewControllerSwift: UIViewController { + + // MARK: - IBOutlets + + @IBOutlet private weak var mapView: GMSMapView! + @IBOutlet private weak var tripSlider: OBSlider! + @IBOutlet private weak var speedLabel: UILabel! + @IBOutlet private weak var timeLabel: UILabel! + @IBOutlet private weak var distanceLabel: UILabel! + @IBOutlet private weak var speedGauge: WMGaugeView! + @IBOutlet private weak var RPMGauge: WMGaugeView! + @IBOutlet private weak var fuelGauge: WMGaugeView! + @IBOutlet private weak var speedometerIcon: UIImageView! + @IBOutlet private weak var fullScreenButton: UIButton! + @IBOutlet private weak var followMeButton: UIButton! + + // MARK: - Properties + + var trip: Trip! + + private let pathColors: [UIColor] = [.red, .orange, .yellow, .green] + private var speedDivisions: [Double] = [] + private var gpsLocations: [GPSLocation] = [] + private var pathForTrip: GMSMutablePath! + private var markerForSlider: GMSMarker? + private var markerForTap: GMSMarker? + private var cameraBounds: GMSCoordinateBounds! + private var isFollowingMe = false + private var showRealTime = false + + private lazy var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + return formatter + }() + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + loadTripData() + setupGoogleMaps() + setupGauges() + setupSlider() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + let update = GMSCameraUpdate.fit(cameraBounds, withPadding: 40) + mapView.animate(with: update) + } + + override var shouldAutorotate: Bool { + return true + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } + + override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + return .portrait + } + + // MARK: - Setup + + private func setupUI() { + speedometerIcon.image = MyStyleKit.image(ofSpeedometerWithStrokeColor: .white) + + let buttonImage = MyStyleKit.image(ofVBoxButtonWithButtonColor: .white) + fullScreenButton.setBackgroundImage(buttonImage, for: .normal) + followMeButton.setBackgroundImage(buttonImage, for: .normal) + + fullScreenButton.layer.masksToBounds = true + fullScreenButton.layer.cornerRadius = 5.0 + + followMeButton.layer.masksToBounds = true + followMeButton.layer.cornerRadius = 5.0 + + // Setup time label tap gesture + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapTimeLabel)) + timeLabel.isUserInteractionEnabled = true + timeLabel.addGestureRecognizer(tapRecognizer) + + followMeButton.isHidden = true + } + + private func loadTripData() { + guard let locations = trip.gpsLocations else { + gpsLocations = [] + return + } + gpsLocations = locations.array as? [GPSLocation] ?? [] + } + + private func setupGoogleMaps() { + mapView.padding = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0) + + pathForTrip = GMSMutablePath() + + var spanStyles: [GMSStyleSpan] = [] + var segments: Double = 1 + var currentColor: UIColor? + var newColor: UIColor? + + guard gpsLocations.count >= 2, + let start = gpsLocations.first, + let end = gpsLocations.last else { + return + } + + // Create start and end markers + let startCoord = CLLocationCoordinate2D(latitude: start.latitude?.doubleValue ?? 0, + longitude: start.longitude?.doubleValue ?? 0) + let endCoord = CLLocationCoordinate2D(latitude: end.latitude?.doubleValue ?? 0, + longitude: end.longitude?.doubleValue ?? 0) + + let startMarker = GMSMarker(position: startCoord) + let endMarker = GMSMarker(position: endCoord) + + startMarker.groundAnchor = CGPoint(x: 0.5, y: 0.5) + endMarker.groundAnchor = CGPoint(x: 0.5, y: 0.5) + + startMarker.map = mapView + endMarker.map = mapView + + startMarker.icon = UIImage(named: "startPosition") + endMarker.icon = UIImage(named: "endPosition") + + // Calculate speed boundaries + speedDivisions = calculateSpeedBoundaries() + + // Build path with color segments + for gpsLoc in gpsLocations { + let lat = gpsLoc.latitude?.doubleValue ?? 0 + let lon = gpsLoc.longitude?.doubleValue ?? 0 + pathForTrip.add(CLLocationCoordinate2D(latitude: lat, longitude: lon)) + + let speed = gpsLoc.speed?.doubleValue ?? 0 + + for (index, bound) in speedDivisions.enumerated() { + if speed <= bound { + newColor = pathColors[index] + if newColor == currentColor { + segments += 1 + } else { + spanStyles.append(GMSStyleSpan(color: currentColor ?? newColor ?? .green, segments: segments)) + segments = 1 + } + currentColor = newColor + break + } + } + } + + // Create polyline + let polyline = GMSPolyline(path: pathForTrip) + polyline.strokeWidth = 5 + polyline.spans = spanStyles + polyline.geodesic = true + polyline.map = mapView + + // Setup camera + cameraBounds = GMSCoordinateBounds(path: pathForTrip) + let camera = mapView.camera(for: cameraBounds, insets: .zero) + + let zoom = (camera?.zoom ?? 10) > 5 ? (camera?.zoom ?? 10) - 4 : (camera?.zoom ?? 10) + mapView.camera = GMSCameraPosition(latitude: startCoord.latitude, + longitude: startCoord.longitude, + zoom: zoom, + bearing: 120, + viewingAngle: 25) + mapView.settings.compassButton = true + mapView.isMyLocationEnabled = false + mapView.delegate = self + } + + private func setupGauges() { + speedGauge.setUp(withUnits: "MPH", max: 150, startAngle: 90, endAngle: 270) + fuelGauge.setUp(withUnits: "Fuel %", max: 100, startAngle: 90, endAngle: 270) + RPMGauge.setUp(withUnits: "RPM", max: 10000, startAngle: 90, endAngle: 270) + } + + private func setupSlider() { + guard !gpsLocations.isEmpty else { return } + tripSlider.maximumValue = Float(gpsLocations.count - 1) + } + + // MARK: - Helper Methods + + private func calculateSpeedBoundaries() -> [Double] { + let maxSpeed = trip.maxSpeed?.doubleValue ?? 0 + let minSpeed = trip.minSpeed?.doubleValue ?? 0 + + var colorDivision: [Double] = [] + for i in 0..<(pathColors.count - 1) { + let bound = (minSpeed + Double(i + 1) * (maxSpeed - minSpeed)) / Double(pathColors.count) + colorDivision.append(bound) + } + colorDivision.append(maxSpeed) + return colorDivision + } + + private func updateMarkerForSlider(with location: GPSLocation) { + let coordinate = CLLocationCoordinate2D(latitude: location.latitude?.doubleValue ?? 0, + longitude: location.longitude?.doubleValue ?? 0) + if markerForSlider == nil { + markerForSlider = GMSMarker(position: coordinate) + markerForSlider?.icon = UIImage(named: "currentLocation") + markerForSlider?.groundAnchor = CGPoint(x: 0.5, y: 0.5) + markerForSlider?.map = mapView + followMeButton.isHidden = false + } else { + CATransaction.begin() + CATransaction.setAnimationDuration(0.001) + markerForSlider?.position = coordinate + CATransaction.commit() + } + + if isFollowingMe { + mapView.animate(toLocation: coordinate) + } + } + + private func updateTapMarker(in mapView: GMSMapView, with location: GPSLocation) { + let coordinate = CLLocationCoordinate2D(latitude: location.latitude?.doubleValue ?? 0, + longitude: location.longitude?.doubleValue ?? 0) + if markerForTap == nil { + markerForTap = GMSMarker(position: coordinate) + markerForTap?.map = mapView + markerForTap?.appearAnimation = .pop + } + + markerForTap?.position = coordinate + let timestampString = location.timestamp.map { dateFormatter.string(from: $0) } ?? "--:--" + markerForTap?.snippet = "Time: \(timestampString)\nSpeed: \(String(format: "%.2f", location.speed?.doubleValue ?? 0))" + } + + // MARK: - Actions + + @IBAction private func sliderValueChanged(_ sender: UISlider) { + let value = Int(round(sender.value)) + guard value < gpsLocations.count else { return } + + let location = gpsLocations[value] + + // Update time label + if showRealTime { + timeLabel.text = location.timestamp.map { dateFormatter.string(from: $0) } ?? "--:--" + } else if let startTime = trip.startTime, let timestamp = location.timestamp { + timeLabel.text = DurationFormatter.string(from: timestamp.timeIntervalSince(startTime)) + } + + // Update speed and distance labels + let speed = location.speed?.doubleValue ?? 0 + let metersFromStart = location.metersFromStart?.doubleValue ?? 0 + speedLabel.text = String(format: "%.2fmph", speed) + distanceLabel.text = String(format: "%.2fmi", metersFromStart * 0.000621371) + + // Update gauges + speedGauge.setValue(Float(speed), animated: false) + + if let bluetoothInfo = location.bluetoothInfo { + RPMGauge.isHidden = false + fuelGauge.isHidden = false + + let rpm = bluetoothInfo.rpm?.floatValue ?? 0 + let fuel = bluetoothInfo.fuel?.floatValue ?? 0 + let btSpeed = bluetoothInfo.speed?.floatValue ?? Float(speed) + + RPMGauge.setValue(rpm, animated: false) + fuelGauge.setValue(fuel, animated: false) + speedGauge.setValue(btSpeed, animated: false) + } + + updateMarkerForSlider(with: location) + } + + @IBAction private func fullScreenButtonTapped(_ sender: UIButton) { + let update = GMSCameraUpdate.fit(cameraBounds, withPadding: 40) + mapView.animate(with: update) + } + + @IBAction private func followMeButtonTapped(_ sender: UIButton) { + isFollowingMe.toggle() + + if isFollowingMe { + sender.setImage(UIImage(named: "followMeOn"), for: .normal) + if let position = markerForSlider?.position { + mapView.animate(toLocation: position) + } + } else { + sender.setImage(UIImage(named: "followMeOff"), for: .normal) + } + } + + @objc private func didTapTimeLabel() { + showRealTime.toggle() + sliderValueChanged(tripSlider) + } + + @IBAction private func shareButtonTapped(_ sender: Any) { + let logContent = generateTripLogString() + + guard let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return + } + + let fileURL = path.appendingPathComponent("trip.log") + + do { + // Remove previous file if exists + if FileManager.default.fileExists(atPath: fileURL.path) { + try FileManager.default.removeItem(at: fileURL) + } + + // Write new log file + try logContent.write(to: fileURL, atomically: true, encoding: .utf8) + + // Present mail composer + if MFMailComposeViewController.canSendMail() { + let composer = MFMailComposeViewController() + composer.mailComposeDelegate = self + composer.setSubject("Trip logged with vBox") + + if let data = FileManager.default.contents(atPath: fileURL.path) { + composer.addAttachmentData(data, mimeType: "text/plain", fileName: "myTrip.log") + } + + let htmlBody = """ + Click here and upload your file to view your log in more detail! +
Brought to you by vBox. + """ + composer.setMessageBody(htmlBody, isHTML: true) + + present(composer, animated: true) { + try? FileManager.default.removeItem(at: fileURL) + } + } else { + SVProgressHUD.showError(withStatus: "This device cannot send mail!") + } + } catch { + SVProgressHUD.showError(withStatus: "Failed to create log file") + } + } + + private func generateTripLogString() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMddHHmmssSSS" + + var log = "Timestamp Lat Long Distance-mi Speed-MPH Altitude-ft RPM-RPM Throttle-% EngineLoad-% Fuel-% Barometric-kPa AmbientTemperature-C CoolantTemperature-C, IntakeTemperature-C, Distance-km\n" + + for location in gpsLocations { + let timestamp = location.timestamp.map { formatter.string(from: $0) } ?? "0" + let lat = location.latitude?.doubleValue ?? 0 + let lon = location.longitude?.doubleValue ?? 0 + let distance = (location.metersFromStart?.doubleValue ?? 0) * 0.000621371 + + log += "\(timestamp) \(lat) \(lon) \(distance)" + + if let bt = location.bluetoothInfo { + let speed = bt.speed?.stringValue ?? location.speed?.stringValue ?? "XX" + let altitude = location.altitude?.doubleValue ?? 0 + let rpm = bt.rpm?.stringValue ?? "XX" + let throttle = bt.throttle?.stringValue ?? "XX" + let engineLoad = bt.engineLoad?.stringValue ?? "XX" + let fuel = bt.fuel?.stringValue ?? "XX" + let barometric = bt.barometric?.stringValue ?? "XX" + let ambient = bt.ambientTemp?.stringValue ?? "XX" + let coolant = bt.coolantTemp?.stringValue ?? "XX" + let intake = bt.intakeTemp?.stringValue ?? "XX" + let btDistance = bt.distance?.stringValue ?? "XX" + + log += " \(speed) \(altitude) \(rpm) \(throttle) \(engineLoad) \(fuel) \(barometric) \(ambient) \(coolant) \(intake) \(btDistance)" + } else { + let speed = location.speed?.stringValue ?? "XX" + let altitude = location.altitude?.doubleValue ?? 0 + log += " \(speed) \(altitude) XX XX XX XX XX XX XX XX XX" + } + log += "\n" + } + + return log + } +} + +// MARK: - GMSMapViewDelegate + +extension TripDetailViewControllerSwift: GMSMapViewDelegate { + func mapView(_ mapView: GMSMapView, willMove gesture: Bool) { + if gesture && isFollowingMe { + followMeButtonTapped(followMeButton) + } + } + + func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) { + let tolerance = pow(10.0, (-0.301 * Double(mapView.camera.zoom)) + 9.0731) / 500 + + guard GMSGeometryIsLocationOnPath(coordinate, pathForTrip, false, tolerance) else { + return + } + + var closestLocation: GPSLocation? + var closestDistance = CLLocationDistanceMax + + for location in gpsLocations { + let coord = CLLocationCoordinate2D(latitude: location.latitude?.doubleValue ?? 0, + longitude: location.longitude?.doubleValue ?? 0) + let distance = GMSGeometryDistance(coord, coordinate) + if distance < closestDistance { + closestDistance = distance + closestLocation = location + } + } + + if let location = closestLocation { + updateTapMarker(in: mapView, with: location) + } + } +} + +// MARK: - MFMailComposeViewControllerDelegate + +extension TripDetailViewControllerSwift: MFMailComposeViewControllerDelegate { + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + dismiss(animated: true) { + switch result { + case .cancelled: + SVProgressHUD.showError(withStatus: "Canceled") + case .failed: + SVProgressHUD.showError(withStatus: "Something went wrong :(") + case .saved: + SVProgressHUD.showSuccess(withStatus: "Saved!") + case .sent: + SVProgressHUD.showSuccess(withStatus: "Sent!") + @unknown default: + break + } + } + } +} diff --git a/vBox/vBox-Bridging-Header.h b/vBox/vBox-Bridging-Header.h new file mode 100644 index 0000000..946f937 --- /dev/null +++ b/vBox/vBox-Bridging-Header.h @@ -0,0 +1,38 @@ +// +// vBox-Bridging-Header.h +// vBox +// +// Bridging header to expose Objective-C code to Swift +// + +#ifndef vBox_Bridging_Header_h +#define vBox_Bridging_Header_h + +// Core Data Models +#import "DrivingHistory.h" +#import "Trip.h" +#import "GPSLocation.h" +#import "BluetoothData.h" + +// Managers +#import "BLEManager.h" + +// View Controllers +#import "AppDelegate.h" +#import "MainScreenViewController.h" +#import "GoogleMapsViewController.h" +#import "TripDetailViewController.h" +#import "DrivingHistoryViewController.h" +#import "BluetoothTableViewController.h" +#import "DebugBluetoothViewController.h" + +// Utilities +#import "UtilityMethods.h" +#import "MyStyleKit.h" + +// Third-party +#import "WMGaugeView.h" +#import "OBSlider.h" +#import "SVProgressHUD.h" + +#endif /* vBox_Bridging_Header_h */ diff --git a/vBoxTests/BLEManagerSwiftTests.swift b/vBoxTests/BLEManagerSwiftTests.swift new file mode 100644 index 0000000..7f749db --- /dev/null +++ b/vBoxTests/BLEManagerSwiftTests.swift @@ -0,0 +1,281 @@ +// +// BLEManagerSwiftTests.swift +// vBoxTests +// +// Tests for BLEManagerSwift +// + +import XCTest +import CoreBluetooth +import Combine +@testable import vBox + +// MARK: - BLE Manager Tests + +final class BLEManagerSwiftTests: XCTestCase { + + var cancellables: Set! + + override func setUp() { + super.setUp() + cancellables = [] + } + + override func tearDown() { + cancellables = nil + super.tearDown() + } + + // MARK: - Initialization Tests + + func testSharedInstanceExists() { + let manager = BLEManagerSwift.shared + XCTAssertNotNil(manager) + } + + func testSharedInstanceIsSingleton() { + let manager1 = BLEManagerSwift.shared + let manager2 = BLEManagerSwift.shared + XCTAssertTrue(manager1 === manager2) + } + + func testInitialStateIsNotConnected() { + let manager = BLEManagerSwift() + XCTAssertFalse(manager.isConnected) + } + + func testInitialStateIsNotScanning() { + let manager = BLEManagerSwift() + XCTAssertFalse(manager.isScanning) + } + + func testInitialDiagnosticsAreEmpty() { + let manager = BLEManagerSwift() + XCTAssertNil(manager.diagnostics.speed) + XCTAssertNil(manager.diagnostics.rpm) + XCTAssertNil(manager.diagnostics.fuelLevel) + } + + // MARK: - State Publisher Tests + + func testStatePublisherEmitsValues() { + let manager = BLEManagerSwift() + let expectation = XCTestExpectation(description: "State publisher emits") + + manager.statePublisher + .sink { _ in + expectation.fulfill() + } + .store(in: &cancellables) + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Connection Publisher Tests + + func testConnectionPublisherEmitsInitialFalse() { + let manager = BLEManagerSwift() + let expectation = XCTestExpectation(description: "Connection publisher emits false") + + manager.connectionPublisher + .first() + .sink { isConnected in + XCTAssertFalse(isConnected) + expectation.fulfill() + } + .store(in: &cancellables) + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Scan Tests + + func testScanFailsWhenBluetoothNotReady() { + let manager = BLEManagerSwift() + // State is initially unknown, not .on + let result = manager.scan(for: .obdAdapter) + XCTAssertFalse(result) + } + + // MARK: - Peripheral Type Tests + + func testOBDAdapterPeripheralType() { + let type = PeripheralType.obdAdapter + XCTAssertEqual(type.serviceUUID, CBUUID(string: "FFE0")) + } + + func testBeagleBonePeripheralType() { + let type = PeripheralType.beagleBone + XCTAssertEqual(type.serviceUUID, CBUUID(string: "FFEF")) + } +} + +// MARK: - BLE Manager Delegate Tests + +final class BLEManagerDelegateTests: XCTestCase { + + // MARK: - Mock Delegate + + class MockDelegate: BLEManagerSwiftDelegate { + var stateChanges: [BLEState] = [] + var diagnosticUpdates: [(key: String, value: NSNumber)] = [] + var didBeginScanningCalled = false + var didStopScanningCalled = false + var didConnectCalled = false + var didDisconnectCalled = false + var logMessages: [String] = [] + + func bleManager(_ manager: BLEManagerSwift, didChangeState state: BLEState) { + stateChanges.append(state) + } + + func bleManager(_ manager: BLEManagerSwift, didUpdateDiagnostic key: String, value: NSNumber) { + diagnosticUpdates.append((key: key, value: value)) + } + + func bleManagerDidBeginScanning(_ manager: BLEManagerSwift) { + didBeginScanningCalled = true + } + + func bleManagerDidStopScanning(_ manager: BLEManagerSwift) { + didStopScanningCalled = true + } + + func bleManagerDidConnect(_ manager: BLEManagerSwift) { + didConnectCalled = true + } + + func bleManagerDidDisconnect(_ manager: BLEManagerSwift) { + didDisconnectCalled = true + } + + func bleManager(_ manager: BLEManagerSwift, didLogMessage message: String) { + logMessages.append(message) + } + } + + func testDelegateCanBeSet() { + let manager = BLEManagerSwift() + let delegate = MockDelegate() + manager.delegate = delegate + XCTAssertNotNil(manager.delegate) + } + + func testDelegateIsWeakReference() { + let manager = BLEManagerSwift() + + autoreleasepool { + let delegate = MockDelegate() + manager.delegate = delegate + XCTAssertNotNil(manager.delegate) + } + + // After autorelease, delegate should be nil (weak reference) + XCTAssertNil(manager.delegate) + } +} + +// MARK: - Combine Integration Tests + +final class BLEManagerCombineTests: XCTestCase { + + var cancellables: Set! + + override func setUp() { + super.setUp() + cancellables = [] + } + + override func tearDown() { + cancellables = nil + super.tearDown() + } + + func testSpeedPublisherExists() { + let manager = BLEManagerSwift() + let publisher = manager.speedPublisher + + let expectation = XCTestExpectation(description: "Speed publisher emits") + + publisher + .first() + .sink { speed in + XCTAssertNil(speed) // Initially nil + expectation.fulfill() + } + .store(in: &cancellables) + + wait(for: [expectation], timeout: 1.0) + } + + func testRPMPublisherExists() { + let manager = BLEManagerSwift() + let publisher = manager.rpmPublisher + + let expectation = XCTestExpectation(description: "RPM publisher emits") + + publisher + .first() + .sink { rpm in + XCTAssertNil(rpm) // Initially nil + expectation.fulfill() + } + .store(in: &cancellables) + + wait(for: [expectation], timeout: 1.0) + } + + func testFuelLevelPublisherExists() { + let manager = BLEManagerSwift() + let publisher = manager.fuelLevelPublisher + + let expectation = XCTestExpectation(description: "Fuel publisher emits") + + publisher + .first() + .sink { fuel in + XCTAssertNil(fuel) // Initially nil + expectation.fulfill() + } + .store(in: &cancellables) + + wait(for: [expectation], timeout: 1.0) + } + + func testDiagnosticsPublisherExists() { + let manager = BLEManagerSwift() + let publisher = manager.diagnosticsPublisher + + let expectation = XCTestExpectation(description: "Diagnostics publisher emits") + + publisher + .first() + .sink { diagnostics in + XCTAssertNotNil(diagnostics) + expectation.fulfill() + } + .store(in: &cancellables) + + wait(for: [expectation], timeout: 1.0) + } +} + +// MARK: - Data Processing Tests + +final class BLEDataProcessingTests: XCTestCase { + + func testValidPacketCreation() { + let data = TestBLEDataBuilder.speedPacket(value: 65.0) + XCTAssertEqual(data.count, 12) + } + + func testRPMPacketCreation() { + let data = TestBLEDataBuilder.rpmPacket(value: 3000.0) + XCTAssertEqual(data.count, 12) + } + + func testFuelPacketCreation() { + let data = TestBLEDataBuilder.fuelPacket(value: 75.0) + XCTAssertEqual(data.count, 12) + } +} diff --git a/vBoxTests/BLETypesTests.swift b/vBoxTests/BLETypesTests.swift new file mode 100644 index 0000000..a07dfbc --- /dev/null +++ b/vBoxTests/BLETypesTests.swift @@ -0,0 +1,286 @@ +// +// BLETypesTests.swift +// vBoxTests +// +// Tests for Bluetooth Low Energy types +// + +import XCTest +import CoreBluetooth +@testable import vBox + +// MARK: - BLE State Tests + +final class BLEStateTests: XCTestCase { + + // MARK: - Raw Values (matching Obj-C BLEState enum) + + func testRawValues() { + XCTAssertEqual(BLEState.unknown.rawValue, 0) + XCTAssertEqual(BLEState.resetting.rawValue, 1) + XCTAssertEqual(BLEState.unsupported.rawValue, 2) + XCTAssertEqual(BLEState.unauthorized.rawValue, 3) + XCTAssertEqual(BLEState.off.rawValue, 4) + XCTAssertEqual(BLEState.on.rawValue, 5) + } + + // MARK: - Description + + func testDescriptions() { + XCTAssertEqual(BLEState.unknown.description, "Unknown") + XCTAssertEqual(BLEState.resetting.description, "Resetting") + XCTAssertEqual(BLEState.unsupported.description, "Unsupported") + XCTAssertEqual(BLEState.unauthorized.description, "Unauthorized") + XCTAssertEqual(BLEState.off.description, "Off") + XCTAssertEqual(BLEState.on.description, "On") + } + + // MARK: - isReady + + func testIsReadyOnlyWhenOn() { + XCTAssertFalse(BLEState.unknown.isReady) + XCTAssertFalse(BLEState.resetting.isReady) + XCTAssertFalse(BLEState.unsupported.isReady) + XCTAssertFalse(BLEState.unauthorized.isReady) + XCTAssertFalse(BLEState.off.isReady) + XCTAssertTrue(BLEState.on.isReady) + } + + // MARK: - requiresUserAction + + func testRequiresUserActionForIssues() { + XCTAssertFalse(BLEState.unknown.requiresUserAction) + XCTAssertFalse(BLEState.resetting.requiresUserAction) + XCTAssertTrue(BLEState.unsupported.requiresUserAction) + XCTAssertTrue(BLEState.unauthorized.requiresUserAction) + XCTAssertTrue(BLEState.off.requiresUserAction) + XCTAssertFalse(BLEState.on.requiresUserAction) + } + + // MARK: - User Messages + + func testUserMessages() { + XCTAssertFalse(BLEState.unknown.userMessage.isEmpty) + XCTAssertFalse(BLEState.resetting.userMessage.isEmpty) + XCTAssertFalse(BLEState.unsupported.userMessage.isEmpty) + XCTAssertFalse(BLEState.unauthorized.userMessage.isEmpty) + XCTAssertFalse(BLEState.off.userMessage.isEmpty) + XCTAssertFalse(BLEState.on.userMessage.isEmpty) + } +} + +// MARK: - Peripheral Type Tests + +final class PeripheralTypeTests: XCTestCase { + + func testOBDAdapterServiceUUID() { + let expected = CBUUID(string: "FFE0") + XCTAssertEqual(PeripheralType.obdAdapter.serviceUUID, expected) + } + + func testBeagleBoneServiceUUID() { + let expected = CBUUID(string: "FFEF") + XCTAssertEqual(PeripheralType.beagleBone.serviceUUID, expected) + } + + func testCharacteristicUUID() { + let expected = CBUUID(string: "FFE1") + XCTAssertEqual(PeripheralType.obdAdapter.characteristicUUID, expected) + XCTAssertEqual(PeripheralType.beagleBone.characteristicUUID, expected) + } + + func testAllCases() { + XCTAssertEqual(PeripheralType.allCases.count, 2) + XCTAssertTrue(PeripheralType.allCases.contains(.obdAdapter)) + XCTAssertTrue(PeripheralType.allCases.contains(.beagleBone)) + } + + func testRawValues() { + XCTAssertEqual(PeripheralType.obdAdapter.rawValue, "OBD") + XCTAssertEqual(PeripheralType.beagleBone.rawValue, "BeagleBone") + } +} + +// MARK: - Connection State Tests + +final class ConnectionStateTests: XCTestCase { + + func testIsConnectedOnlyWhenConnected() { + XCTAssertFalse(ConnectionState.disconnected.isConnected) + XCTAssertFalse(ConnectionState.connecting.isConnected) + XCTAssertTrue(ConnectionState.connected.isConnected) + XCTAssertFalse(ConnectionState.disconnecting.isConnected) + } +} + +// MARK: - Signal Strength Tests + +final class SignalStrengthTests: XCTestCase { + + func testExcellentSignal() { + XCTAssertEqual(SignalStrength(rssi: -30), .excellent) + XCTAssertEqual(SignalStrength(rssi: -49), .excellent) + XCTAssertEqual(SignalStrength(rssi: -50), .excellent) + } + + func testGoodSignal() { + XCTAssertEqual(SignalStrength(rssi: -51), .good) + XCTAssertEqual(SignalStrength(rssi: -60), .good) + XCTAssertEqual(SignalStrength(rssi: -69), .good) + } + + func testFairSignal() { + XCTAssertEqual(SignalStrength(rssi: -70), .fair) + XCTAssertEqual(SignalStrength(rssi: -75), .fair) + XCTAssertEqual(SignalStrength(rssi: -79), .fair) + } + + func testWeakSignal() { + XCTAssertEqual(SignalStrength(rssi: -80), .weak) + XCTAssertEqual(SignalStrength(rssi: -90), .weak) + XCTAssertEqual(SignalStrength(rssi: -100), .weak) + } + + func testBars() { + XCTAssertEqual(SignalStrength.excellent.bars, 4) + XCTAssertEqual(SignalStrength.good.bars, 3) + XCTAssertEqual(SignalStrength.fair.bars, 2) + XCTAssertEqual(SignalStrength.weak.bars, 1) + } + + func testDescriptions() { + XCTAssertEqual(SignalStrength.excellent.description, "Excellent") + XCTAssertEqual(SignalStrength.good.description, "Good") + XCTAssertEqual(SignalStrength.fair.description, "Fair") + XCTAssertEqual(SignalStrength.weak.description, "Weak") + } +} + +// MARK: - BLE Error Tests + +final class BLEErrorTests: XCTestCase { + + func testBluetoothOffDescription() { + let error = BLEError.bluetoothOff + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("off")) + } + + func testBluetoothUnauthorizedDescription() { + let error = BLEError.bluetoothUnauthorized + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("unauthorized")) + } + + func testBluetoothUnsupportedDescription() { + let error = BLEError.bluetoothUnsupported + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("not supported")) + } + + func testNotConnectedDescription() { + let error = BLEError.notConnected + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("not connected")) + } + + func testConnectionFailedWithError() { + let underlyingError = NSError(domain: "test", code: 1, userInfo: [NSLocalizedDescriptionKey: "Test error"]) + let error = BLEError.connectionFailed(underlyingError) + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.contains("Test error")) + } + + func testConnectionFailedWithoutError() { + let error = BLEError.connectionFailed(nil) + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("unknown")) + } + + func testServiceNotFoundDescription() { + let error = BLEError.serviceNotFound + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("service")) + } + + func testCharacteristicNotFoundDescription() { + let error = BLEError.characteristicNotFound + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("characteristic")) + } + + func testWriteErrorWithError() { + let underlyingError = NSError(domain: "test", code: 2, userInfo: [NSLocalizedDescriptionKey: "Write failed"]) + let error = BLEError.writeError(underlyingError) + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.contains("Write failed")) + } + + func testInvalidDataDescription() { + let error = BLEError.invalidData + XCTAssertNotNil(error.errorDescription) + XCTAssertTrue(error.errorDescription!.lowercased().contains("invalid")) + } +} + +// MARK: - Discovered Peripheral Tests + +final class DiscoveredPeripheralTests: XCTestCase { + + func testDisplayNameWithName() { + // We can't easily create a mock CBPeripheral, so we'll test the simpler paths + // Testing the type's logic directly + let peripheral = DiscoveredPeripheral( + identifier: UUID(), + name: "Test Device", + rssi: -50, + advertisementData: [:], + discoveredAt: Date() + ) + XCTAssertEqual(peripheral.displayName, "Test Device") + } + + func testDisplayNameWithoutName() { + let peripheral = DiscoveredPeripheral( + identifier: UUID(), + name: nil, + rssi: -50, + advertisementData: [:], + discoveredAt: Date() + ) + XCTAssertEqual(peripheral.displayName, "Unknown Device") + } + + func testSignalStrengthMapping() { + let excellentPeripheral = DiscoveredPeripheral( + identifier: UUID(), + name: "Test", + rssi: -40, + advertisementData: [:], + discoveredAt: Date() + ) + XCTAssertEqual(excellentPeripheral.signalStrength, .excellent) + + let weakPeripheral = DiscoveredPeripheral( + identifier: UUID(), + name: "Test", + rssi: -90, + advertisementData: [:], + discoveredAt: Date() + ) + XCTAssertEqual(weakPeripheral.signalStrength, .weak) + } +} + +// MARK: - Helper extension for testing DiscoveredPeripheral without CBPeripheral + +extension DiscoveredPeripheral { + /// Test initializer without requiring CBPeripheral + init(identifier: UUID, name: String?, rssi: Int, advertisementData: [String: Any], discoveredAt: Date) { + self.identifier = identifier + self.name = name + self.rssi = rssi + self.advertisementData = advertisementData + self.discoveredAt = discoveredAt + } +} diff --git a/vBoxTests/CoreDataModelTests.swift b/vBoxTests/CoreDataModelTests.swift new file mode 100644 index 0000000..5d8a200 --- /dev/null +++ b/vBoxTests/CoreDataModelTests.swift @@ -0,0 +1,684 @@ +// +// CoreDataModelTests.swift +// vBoxTests +// +// Tests for Swift Core Data models +// + +import XCTest +import CoreData +import CoreLocation +@testable import vBox + +// MARK: - Core Data Test Base + +class CoreDataTestCase: XCTestCase { + + var testContext: NSManagedObjectContext! + + override func setUp() { + super.setUp() + testContext = createInMemoryContext() + } + + override func tearDown() { + testContext = nil + super.tearDown() + } + + /// Create an in-memory Core Data context for testing + func createInMemoryContext() -> NSManagedObjectContext { + // Create managed object model + let model = NSManagedObjectModel() + + // DrivingHistory entity + let drivingHistoryEntity = NSEntityDescription() + drivingHistoryEntity.name = "DrivingHistory" + drivingHistoryEntity.managedObjectClassName = NSStringFromClass(DrivingHistoryEntity.self) + + // Trip entity + let tripEntity = NSEntityDescription() + tripEntity.name = "Trip" + tripEntity.managedObjectClassName = NSStringFromClass(TripEntity.self) + + let tripStartTime = NSAttributeDescription() + tripStartTime.name = "startTime" + tripStartTime.attributeType = .dateAttributeType + tripStartTime.isOptional = true + + let tripEndTime = NSAttributeDescription() + tripEndTime.name = "endTime" + tripEndTime.attributeType = .dateAttributeType + tripEndTime.isOptional = true + + let tripAvgSpeed = NSAttributeDescription() + tripAvgSpeed.name = "avgSpeed" + tripAvgSpeed.attributeType = .doubleAttributeType + tripAvgSpeed.isOptional = true + + let tripMaxSpeed = NSAttributeDescription() + tripMaxSpeed.name = "maxSpeed" + tripMaxSpeed.attributeType = .doubleAttributeType + tripMaxSpeed.isOptional = true + + let tripMinSpeed = NSAttributeDescription() + tripMinSpeed.name = "minSpeed" + tripMinSpeed.attributeType = .doubleAttributeType + tripMinSpeed.isOptional = true + + let tripTotalMiles = NSAttributeDescription() + tripTotalMiles.name = "totalMiles" + tripTotalMiles.attributeType = .doubleAttributeType + tripTotalMiles.isOptional = true + + let tripName = NSAttributeDescription() + tripName.name = "tripName" + tripName.attributeType = .stringAttributeType + tripName.isOptional = true + + tripEntity.properties = [tripStartTime, tripEndTime, tripAvgSpeed, tripMaxSpeed, tripMinSpeed, tripTotalMiles, tripName] + + // GPSLocation entity + let gpsLocationEntity = NSEntityDescription() + gpsLocationEntity.name = "GPSLocation" + gpsLocationEntity.managedObjectClassName = NSStringFromClass(GPSLocationEntity.self) + + let gpsLatitude = NSAttributeDescription() + gpsLatitude.name = "latitude" + gpsLatitude.attributeType = .doubleAttributeType + gpsLatitude.isOptional = true + + let gpsLongitude = NSAttributeDescription() + gpsLongitude.name = "longitude" + gpsLongitude.attributeType = .doubleAttributeType + gpsLongitude.isOptional = true + + let gpsSpeed = NSAttributeDescription() + gpsSpeed.name = "speed" + gpsSpeed.attributeType = .doubleAttributeType + gpsSpeed.isOptional = true + + let gpsAltitude = NSAttributeDescription() + gpsAltitude.name = "altitude" + gpsAltitude.attributeType = .doubleAttributeType + gpsAltitude.isOptional = true + + let gpsMetersFromStart = NSAttributeDescription() + gpsMetersFromStart.name = "metersFromStart" + gpsMetersFromStart.attributeType = .doubleAttributeType + gpsMetersFromStart.isOptional = true + + let gpsTimestamp = NSAttributeDescription() + gpsTimestamp.name = "timestamp" + gpsTimestamp.attributeType = .dateAttributeType + gpsTimestamp.isOptional = true + + gpsLocationEntity.properties = [gpsLatitude, gpsLongitude, gpsSpeed, gpsAltitude, gpsMetersFromStart, gpsTimestamp] + + // BluetoothData entity + let bluetoothDataEntity = NSEntityDescription() + bluetoothDataEntity.name = "BluetoothData" + bluetoothDataEntity.managedObjectClassName = NSStringFromClass(BluetoothDataEntity.self) + + let btSpeed = NSAttributeDescription() + btSpeed.name = "speed" + btSpeed.attributeType = .doubleAttributeType + btSpeed.isOptional = true + + let btRpm = NSAttributeDescription() + btRpm.name = "rpm" + btRpm.attributeType = .doubleAttributeType + btRpm.isOptional = true + + let btFuel = NSAttributeDescription() + btFuel.name = "fuel" + btFuel.attributeType = .doubleAttributeType + btFuel.isOptional = true + + let btCoolantTemp = NSAttributeDescription() + btCoolantTemp.name = "coolantTemp" + btCoolantTemp.attributeType = .doubleAttributeType + btCoolantTemp.isOptional = true + + let btEngineLoad = NSAttributeDescription() + btEngineLoad.name = "engineLoad" + btEngineLoad.attributeType = .doubleAttributeType + btEngineLoad.isOptional = true + + let btThrottle = NSAttributeDescription() + btThrottle.name = "throttle" + btThrottle.attributeType = .doubleAttributeType + btThrottle.isOptional = true + + let btIntakeTemp = NSAttributeDescription() + btIntakeTemp.name = "intakeTemp" + btIntakeTemp.attributeType = .doubleAttributeType + btIntakeTemp.isOptional = true + + let btAmbientTemp = NSAttributeDescription() + btAmbientTemp.name = "ambientTemp" + btAmbientTemp.attributeType = .doubleAttributeType + btAmbientTemp.isOptional = true + + let btBarometric = NSAttributeDescription() + btBarometric.name = "barometric" + btBarometric.attributeType = .doubleAttributeType + btBarometric.isOptional = true + + let btDistance = NSAttributeDescription() + btDistance.name = "distance" + btDistance.attributeType = .doubleAttributeType + btDistance.isOptional = true + + let btAccelX = NSAttributeDescription() + btAccelX.name = "accelX" + btAccelX.attributeType = .doubleAttributeType + btAccelX.isOptional = true + + let btAccelY = NSAttributeDescription() + btAccelY.name = "accelY" + btAccelY.attributeType = .doubleAttributeType + btAccelY.isOptional = true + + let btAccelZ = NSAttributeDescription() + btAccelZ.name = "accelZ" + btAccelZ.attributeType = .doubleAttributeType + btAccelZ.isOptional = true + + bluetoothDataEntity.properties = [btSpeed, btRpm, btFuel, btCoolantTemp, btEngineLoad, btThrottle, btIntakeTemp, btAmbientTemp, btBarometric, btDistance, btAccelX, btAccelY, btAccelZ] + + model.entities = [drivingHistoryEntity, tripEntity, gpsLocationEntity, bluetoothDataEntity] + + // Create persistent store coordinator + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) + try! coordinator.addPersistentStore( + ofType: NSInMemoryStoreType, + configurationName: nil, + at: nil, + options: nil + ) + + // Create context + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.persistentStoreCoordinator = coordinator + return context + } +} + +// MARK: - Trip Entity Tests + +final class TripEntityTests: CoreDataTestCase { + + // MARK: - Creation Tests + + func testCreateTrip() { + let trip = TripEntity(context: testContext) + trip.startTime = Date() + + XCTAssertNotNil(trip) + XCTAssertNotNil(trip.startTime) + } + + func testTripFactoryMethod() { + let trip = TripEntity.create(in: testContext) + + XCTAssertNotNil(trip) + XCTAssertNotNil(trip.startTime) + } + + func testTripFactoryWithDates() { + let start = Date(timeIntervalSince1970: 0) + let end = Date(timeIntervalSince1970: 3600) + + let trip = TripEntity.create(in: testContext, startTime: start, endTime: end, name: "Test Trip") + + XCTAssertEqual(trip.startTime, start) + XCTAssertEqual(trip.endTime, end) + XCTAssertEqual(trip.tripName, "Test Trip") + } + + // MARK: - Duration Tests + + func testDurationCalculation() { + let trip = TripEntity(context: testContext) + trip.startTime = Date(timeIntervalSince1970: 0) + trip.endTime = Date(timeIntervalSince1970: 3723) + + XCTAssertEqual(trip.duration, 3723) + } + + func testDurationStringFormatting() { + let trip = TripEntity(context: testContext) + trip.startTime = Date(timeIntervalSince1970: 0) + trip.endTime = Date(timeIntervalSince1970: 3723) + + XCTAssertEqual(trip.durationString, "01:02:03") + } + + func testDurationNilWhenMissingTimes() { + let trip = TripEntity(context: testContext) + XCTAssertNil(trip.duration) + } + + // MARK: - Speed Tests + + func testSpeedProperties() { + let trip = TripEntity(context: testContext) + trip.avgSpeed = 65.0 + trip.maxSpeed = 85.0 + trip.minSpeed = 35.0 + + XCTAssertEqual(trip.averageSpeed, 65.0) + XCTAssertEqual(trip.maximumSpeed, 85.0) + XCTAssertEqual(trip.minimumSpeed, 35.0) + } + + func testSpeedDefaultsToZero() { + let trip = TripEntity(context: testContext) + + XCTAssertEqual(trip.averageSpeed, 0) + XCTAssertEqual(trip.maximumSpeed, 0) + XCTAssertEqual(trip.minimumSpeed, 0) + } + + // MARK: - Distance Tests + + func testDistanceProperties() { + let trip = TripEntity(context: testContext) + trip.totalMiles = 50.0 + + XCTAssertEqual(trip.distanceMiles, 50.0) + XCTAssertEqual(trip.distanceKilometers, 50.0 * 1.60934, accuracy: 0.01) + } + + // MARK: - Location Count Tests + + func testLocationCountEmpty() { + let trip = TripEntity(context: testContext) + XCTAssertEqual(trip.locationCount, 0) + } +} + +// MARK: - GPS Location Entity Tests + +final class GPSLocationEntityTests: CoreDataTestCase { + + // MARK: - Creation Tests + + func testCreateGPSLocation() { + let location = GPSLocationEntity(context: testContext) + location.latitude = 37.7749 + location.longitude = -122.4194 + + XCTAssertEqual(location.latitude?.doubleValue, 37.7749) + XCTAssertEqual(location.longitude?.doubleValue, -122.4194) + } + + func testCreateFromCLLocation() { + let clLocation = CLLocation( + coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), + altitude: 100, + horizontalAccuracy: 10, + verticalAccuracy: 10, + course: 90, + speed: 20, + timestamp: Date() + ) + + let location = GPSLocationEntity.create(in: testContext, from: clLocation, metersFromStart: 500) + + XCTAssertEqual(location.latitude?.doubleValue, 37.7749, accuracy: 0.0001) + XCTAssertEqual(location.longitude?.doubleValue, -122.4194, accuracy: 0.0001) + XCTAssertEqual(location.speed?.doubleValue, 20) + XCTAssertEqual(location.altitude?.doubleValue, 100) + XCTAssertEqual(location.metersFromStart?.doubleValue, 500) + } + + func testCreateWithCoordinates() { + let location = GPSLocationEntity.create( + in: testContext, + latitude: 34.0522, + longitude: -118.2437, + speed: 30, + altitude: 50, + metersFromStart: 1000 + ) + + XCTAssertEqual(location.latitude?.doubleValue, 34.0522) + XCTAssertEqual(location.longitude?.doubleValue, -118.2437) + XCTAssertEqual(location.speed?.doubleValue, 30) + } + + // MARK: - Coordinate Tests + + func testCoordinateProperty() { + let location = GPSLocationEntity(context: testContext) + location.latitude = 37.7749 + location.longitude = -122.4194 + + let coordinate = location.coordinate + + XCTAssertNotNil(coordinate) + XCTAssertEqual(coordinate?.latitude, 37.7749) + XCTAssertEqual(coordinate?.longitude, -122.4194) + } + + func testCoordinateNilWhenMissing() { + let location = GPSLocationEntity(context: testContext) + XCTAssertNil(location.coordinate) + } + + // MARK: - Speed Conversion Tests + + func testSpeedConversions() { + let location = GPSLocationEntity(context: testContext) + location.speed = 20.0 // 20 m/s + + XCTAssertEqual(location.speedMetersPerSecond, 20.0) + XCTAssertEqual(location.speedKmh, 72.0, accuracy: 0.1) // 20 * 3.6 + XCTAssertEqual(location.speedMph, 44.7, accuracy: 0.1) // 20 * 2.23694 + } + + // MARK: - Distance Conversion Tests + + func testDistanceConversions() { + let location = GPSLocationEntity(context: testContext) + location.metersFromStart = 10000.0 // 10 km + + XCTAssertEqual(location.distanceFromStart, 10000.0) + XCTAssertEqual(location.distanceFromStartKm, 10.0) + XCTAssertEqual(location.distanceFromStartMiles, 6.21, accuracy: 0.01) + } + + // MARK: - CLLocation Conversion Tests + + func testCLLocationConversion() { + let location = GPSLocationEntity(context: testContext) + location.latitude = 37.7749 + location.longitude = -122.4194 + location.altitude = 100 + location.speed = 20 + location.timestamp = Date() + + let clLocation = location.clLocation + + XCTAssertNotNil(clLocation) + XCTAssertEqual(clLocation?.coordinate.latitude, 37.7749) + XCTAssertEqual(clLocation?.coordinate.longitude, -122.4194) + } + + // MARK: - Bluetooth Data Association Tests + + func testHasBluetoothDataFalseWhenNil() { + let location = GPSLocationEntity(context: testContext) + XCTAssertFalse(location.hasBluetoothData) + } +} + +// MARK: - Bluetooth Data Entity Tests + +final class BluetoothDataEntityTests: CoreDataTestCase { + + // MARK: - Creation Tests + + func testCreateBluetoothData() { + let data = BluetoothDataEntity(context: testContext) + data.speed = 65.0 + data.rpm = 3000.0 + data.fuel = 75.0 + + XCTAssertEqual(data.speed?.doubleValue, 65.0) + XCTAssertEqual(data.rpm?.doubleValue, 3000.0) + XCTAssertEqual(data.fuel?.doubleValue, 75.0) + } + + func testFactoryMethod() { + let data = BluetoothDataEntity.create( + in: testContext, + speed: 60, + rpm: 2500, + fuel: 80, + coolantTemp: 90 + ) + + XCTAssertEqual(data.speedKmh, 60) + XCTAssertEqual(data.engineRpm, 2500) + XCTAssertEqual(data.fuelLevel, 80) + XCTAssertEqual(data.coolantTemperature, 90) + } + + // MARK: - Speed Tests + + func testSpeedConversions() { + let data = BluetoothDataEntity(context: testContext) + data.speed = 100.0 // 100 km/h + + XCTAssertEqual(data.speedKmh, 100.0) + XCTAssertEqual(data.speedMph!, 62.14, accuracy: 0.01) + } + + // MARK: - Fuel Tests + + func testFuelLevel() { + let data = BluetoothDataEntity(context: testContext) + data.fuel = 75.0 + + XCTAssertEqual(data.fuelLevel, 75.0) + XCTAssertFalse(data.isFuelLow) + } + + func testFuelLowWarning() { + let data = BluetoothDataEntity(context: testContext) + data.fuel = 10.0 + + XCTAssertTrue(data.isFuelLow) + } + + func testFuelLowThreshold() { + let data = BluetoothDataEntity(context: testContext) + + data.fuel = 14.9 + XCTAssertTrue(data.isFuelLow) + + data.fuel = 15.0 + XCTAssertFalse(data.isFuelLow) + } + + // MARK: - Temperature Tests + + func testCoolantTemperature() { + let data = BluetoothDataEntity(context: testContext) + data.coolantTemp = 90.0 + + XCTAssertEqual(data.coolantTemperature, 90.0) + XCTAssertEqual(data.coolantTemperatureFahrenheit!, 194.0, accuracy: 0.1) + } + + func testOperatingTemperature() { + let data = BluetoothDataEntity(context: testContext) + + data.coolantTemp = 70.0 + XCTAssertFalse(data.isAtOperatingTemperature) + + data.coolantTemp = 90.0 + XCTAssertTrue(data.isAtOperatingTemperature) + + data.coolantTemp = 110.0 + XCTAssertFalse(data.isAtOperatingTemperature) + } + + func testOverheating() { + let data = BluetoothDataEntity(context: testContext) + + data.coolantTemp = 100.0 + XCTAssertFalse(data.isOverheating) + + data.coolantTemp = 106.0 + XCTAssertTrue(data.isOverheating) + } + + // MARK: - Accelerometer Tests + + func testAccelerometerValues() { + let data = BluetoothDataEntity(context: testContext) + data.accelX = 0.1 + data.accelY = 0.2 + data.accelZ = 0.3 + + XCTAssertEqual(data.accelerationX, 0.1) + XCTAssertEqual(data.accelerationY, 0.2) + XCTAssertEqual(data.accelerationZ, 0.3) + } + + func testAccelerometerMagnitude() { + let data = BluetoothDataEntity(context: testContext) + data.accelX = 3.0 + data.accelY = 4.0 + data.accelZ = 0.0 + + XCTAssertEqual(data.accelerationMagnitude!, 5.0, accuracy: 0.001) + } + + func testAccelerometerTuple() { + let data = BluetoothDataEntity(context: testContext) + data.accelX = 1.0 + data.accelY = 2.0 + data.accelZ = 3.0 + + let accel = data.acceleration + + XCTAssertNotNil(accel) + XCTAssertEqual(accel?.x, 1.0) + XCTAssertEqual(accel?.y, 2.0) + XCTAssertEqual(accel?.z, 3.0) + } + + // MARK: - Update Methods Tests + + func testUpdateFromDiagnostics() { + let data = BluetoothDataEntity(context: testContext) + + var diagnostics = VehicleDiagnostics() + diagnostics.update(with: DiagnosticReading(pid: .speed, value: 65.0)) + diagnostics.update(with: DiagnosticReading(pid: .rpm, value: 3000.0)) + diagnostics.update(with: DiagnosticReading(pid: .fuelLevel, value: 75.0)) + + data.update(from: diagnostics) + + XCTAssertEqual(data.speedKmh, 65.0) + XCTAssertEqual(data.engineRpm, 3000.0) + XCTAssertEqual(data.fuelLevel, 75.0) + } + + func testUpdateFromDiagnosticReading() { + let data = BluetoothDataEntity(context: testContext) + + data.update(from: DiagnosticReading(pid: .throttle, value: 30.0)) + + XCTAssertEqual(data.throttlePosition, 30.0) + } + + func testUpdateAccelerometer() { + let data = BluetoothDataEntity(context: testContext) + + data.updateAccelerometer(x: 0.5, y: -0.3, z: 9.8) + + XCTAssertEqual(data.accelerationX, 0.5) + XCTAssertEqual(data.accelerationY, -0.3) + XCTAssertEqual(data.accelerationZ, 9.8) + } + + // MARK: - Dictionary Export Tests + + func testDictionaryRepresentation() { + let data = BluetoothDataEntity(context: testContext) + data.speed = 65.0 + data.rpm = 3000.0 + + let dict = data.dictionaryRepresentation + + XCTAssertEqual(dict["speed"] as? NSNumber, 65.0) + XCTAssertEqual(dict["rpm"] as? NSNumber, 3000.0) + XCTAssertNil(dict["fuel"]) // Not set, shouldn't be in dict + } +} + +// MARK: - Driving History Entity Tests + +final class DrivingHistoryEntityTests: CoreDataTestCase { + + // MARK: - Creation Tests + + func testCreateDrivingHistory() { + let history = DrivingHistoryEntity(context: testContext) + XCTAssertNotNil(history) + XCTAssertEqual(history.tripCount, 0) + } + + func testGetOrCreate() { + let history1 = DrivingHistoryEntity.getOrCreate(in: testContext) + try! testContext.save() + + let history2 = DrivingHistoryEntity.getOrCreate(in: testContext) + + XCTAssertEqual(history1, history2) + } + + // MARK: - Trip Management Tests + + func testTripCount() { + let history = DrivingHistoryEntity(context: testContext) + + XCTAssertEqual(history.tripCount, 0) + } + + func testTripsArrayEmpty() { + let history = DrivingHistoryEntity(context: testContext) + + XCTAssertEqual(history.tripsArray.count, 0) + } + + // MARK: - Statistics Tests + + func testStatisticsEmpty() { + let history = DrivingHistoryEntity(context: testContext) + let stats = history.statistics + + XCTAssertEqual(stats.totalTrips, 0) + XCTAssertEqual(stats.totalDistanceMiles, 0) + XCTAssertEqual(stats.totalDrivingTime, 0) + } +} + +// MARK: - Driving Statistics Tests + +final class DrivingStatisticsTests: XCTestCase { + + func testDistanceConversion() { + let stats = DrivingStatistics( + totalTrips: 10, + totalDistanceMiles: 100, + totalDrivingTime: 3600, + averageTripDistanceMiles: 10, + averageTripDuration: 360, + maxSpeedRecorded: 85, + averageSpeed: 45 + ) + + XCTAssertEqual(stats.totalDistanceKilometers, 160.934, accuracy: 0.01) + } + + func testFormattedTotalTime() { + let stats = DrivingStatistics( + totalTrips: 5, + totalDistanceMiles: 50, + totalDrivingTime: 7200, // 2 hours + averageTripDistanceMiles: 10, + averageTripDuration: 1440, + maxSpeedRecorded: 75, + averageSpeed: 40 + ) + + XCTAssertEqual(stats.formattedTotalTime, "2 hours") + } +} diff --git a/vBoxTests/DateFormattingTests.swift b/vBoxTests/DateFormattingTests.swift new file mode 100644 index 0000000..a4d9918 --- /dev/null +++ b/vBoxTests/DateFormattingTests.swift @@ -0,0 +1,279 @@ +// +// DateFormattingTests.swift +// vBoxTests +// +// Tests for date and time formatting utilities +// + +import XCTest +@testable import vBox + +// MARK: - Duration Formatter Tests + +final class DurationFormatterTests: XCTestCase { + + // MARK: - Basic Duration Formatting + + func testZeroDuration() { + XCTAssertEqual(DurationFormatter.string(from: 0), "00:00:00") + } + + func testSecondsOnly() { + XCTAssertEqual(DurationFormatter.string(from: 45), "00:00:45") + } + + func testMinutesAndSeconds() { + XCTAssertEqual(DurationFormatter.string(from: 125), "00:02:05") + } + + func testHoursMinutesSeconds() { + XCTAssertEqual(DurationFormatter.string(from: 3723), "01:02:03") + } + + func testExactlyOneHour() { + XCTAssertEqual(DurationFormatter.string(from: 3600), "01:00:00") + } + + func testMultipleHours() { + XCTAssertEqual(DurationFormatter.string(from: 36000), "10:00:00") + } + + func testLargeDuration() { + // 99 hours, 59 minutes, 59 seconds + XCTAssertEqual(DurationFormatter.string(from: 359999), "99:59:59") + } + + // MARK: - Duration Between Dates + + func testDurationBetweenDates() { + let start = Date(timeIntervalSince1970: 0) + let end = Date(timeIntervalSince1970: 3723) + + XCTAssertEqual(DurationFormatter.string(from: start, to: end), "01:02:03") + } + + func testDurationBetweenSameDates() { + let date = Date() + XCTAssertEqual(DurationFormatter.string(from: date, to: date), "00:00:00") + } + + // MARK: - Human Readable Duration + + func testHumanReadableSeconds() { + XCTAssertEqual(DurationFormatter.humanReadable(from: 1), "1 second") + XCTAssertEqual(DurationFormatter.humanReadable(from: 30), "30 seconds") + } + + func testHumanReadableMinutes() { + XCTAssertEqual(DurationFormatter.humanReadable(from: 60), "1 minute") + XCTAssertEqual(DurationFormatter.humanReadable(from: 120), "2 minutes") + XCTAssertEqual(DurationFormatter.humanReadable(from: 300), "5 minutes") + } + + func testHumanReadableHours() { + XCTAssertEqual(DurationFormatter.humanReadable(from: 3600), "1 hour") + XCTAssertEqual(DurationFormatter.humanReadable(from: 7200), "2 hours") + } + + func testHumanReadableHoursAndMinutes() { + XCTAssertEqual(DurationFormatter.humanReadable(from: 3660), "1 hour 1 minute") + XCTAssertEqual(DurationFormatter.humanReadable(from: 7500), "2 hours 5 minutes") + } +} + +// MARK: - Date Display Formatter Tests + +final class DateDisplayFormatterTests: XCTestCase { + + // MARK: - Full Date Time + + func testFullDateTimeFormatIncludesExpectedComponents() { + let date = createDate(year: 2024, month: 3, day: 15, hour: 14, minute: 30, second: 45) + let result = DateDisplayFormatter.fullDateTime(date) + + // The format is "MMMM dd, yyyy (EEEE) HH:mm:ss" + XCTAssertTrue(result.contains("March")) + XCTAssertTrue(result.contains("15")) + XCTAssertTrue(result.contains("2024")) + XCTAssertTrue(result.contains("14:30:45")) + } + + // MARK: - Short Date + + func testShortDateReturnsNonEmptyString() { + let date = Date() + let result = DateDisplayFormatter.shortDate(date) + XCTAssertFalse(result.isEmpty) + } + + // MARK: - Short Time + + func testShortTimeReturnsNonEmptyString() { + let date = Date() + let result = DateDisplayFormatter.shortTime(date) + XCTAssertFalse(result.isEmpty) + } + + // MARK: - Medium Date Time + + func testMediumDateTimeReturnsNonEmptyString() { + let date = Date() + let result = DateDisplayFormatter.mediumDateTime(date) + XCTAssertFalse(result.isEmpty) + } + + // MARK: - Day of Week + + func testDayOfWeekFriday() { + // March 15, 2024 is a Friday + let date = createDate(year: 2024, month: 3, day: 15) + let result = DateDisplayFormatter.dayOfWeek(date) + XCTAssertEqual(result, "Friday") + } + + func testDayOfWeekSunday() { + // March 17, 2024 is a Sunday + let date = createDate(year: 2024, month: 3, day: 17) + let result = DateDisplayFormatter.dayOfWeek(date) + XCTAssertEqual(result, "Sunday") + } + + // MARK: - Month Day + + func testMonthDay() { + let date = createDate(year: 2024, month: 3, day: 15) + let result = DateDisplayFormatter.monthDay(date) + XCTAssertEqual(result, "March 15") + } + + func testMonthDayJanuary() { + let date = createDate(year: 2024, month: 1, day: 1) + let result = DateDisplayFormatter.monthDay(date) + XCTAssertEqual(result, "January 1") + } + + // MARK: - Relative Date + + func testRelativeDateToday() { + let today = Date() + let result = DateDisplayFormatter.relativeDate(today) + XCTAssertEqual(result, "Today") + } + + func testRelativeDateYesterday() { + let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! + let result = DateDisplayFormatter.relativeDate(yesterday) + XCTAssertEqual(result, "Yesterday") + } + + func testRelativeDateOlderDate() { + // A date from last month should return short date + let oldDate = Calendar.current.date(byAdding: .month, value: -1, to: Date())! + let result = DateDisplayFormatter.relativeDate(oldDate) + // Should be short date format, not "Today" or "Yesterday" + XCTAssertNotEqual(result, "Today") + XCTAssertNotEqual(result, "Yesterday") + } + + // MARK: - Trip Timestamp + + func testTripTimestampToday() { + let today = Date() + let result = DateDisplayFormatter.tripTimestamp(today) + XCTAssertTrue(result.hasPrefix("Today at ")) + } + + func testTripTimestampYesterday() { + let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! + let result = DateDisplayFormatter.tripTimestamp(yesterday) + XCTAssertTrue(result.hasPrefix("Yesterday at ")) + } + + // MARK: - Helper Methods + + private func createDate(year: Int, month: Int, day: Int, hour: Int = 12, minute: Int = 0, second: Int = 0) -> Date { + var components = DateComponents() + components.year = year + components.month = month + components.day = day + components.hour = hour + components.minute = minute + components.second = second + components.timeZone = TimeZone.current + return Calendar.current.date(from: components)! + } +} + +// MARK: - Date Extension Tests + +final class DateExtensionTests: XCTestCase { + + func testFullDateTimeString() { + let date = Date() + let result = date.fullDateTimeString + XCTAssertFalse(result.isEmpty) + } + + func testRelativeDateString() { + let date = Date() + XCTAssertEqual(date.relativeDateString, "Today") + } +} + +// MARK: - TimeInterval Extension Tests + +final class TimeIntervalExtensionTests: XCTestCase { + + func testDurationString() { + let interval: TimeInterval = 3723 + XCTAssertEqual(interval.durationString, "01:02:03") + } + + func testHumanReadableDuration() { + let interval: TimeInterval = 7500 + XCTAssertEqual(interval.humanReadableDuration, "2 hours 5 minutes") + } +} + +// MARK: - Backward Compatibility Tests + +final class UtilityMethodsCompatibilityTests: XCTestCase { + + /// Test that Swift formatting matches the Objective-C UtilityMethods output + func testDurationStringMatchesObjCFormat() { + // The Obj-C method returns "HH:MM:SS" format + let start = Date(timeIntervalSince1970: 0) + let end = Date(timeIntervalSince1970: 3723) // 1 hour, 2 min, 3 sec + + let swiftResult = DurationFormatter.string(from: start, to: end) + + // Should match Obj-C format exactly + XCTAssertEqual(swiftResult, "01:02:03") + } + + func testFullDateTimeMatchesObjCFormat() { + // The Obj-C format is "MMMM dd, yyy (EEEE) HH:mm:ss" + // Note: There's a typo in the Obj-C ("yyy" instead of "yyyy") but it still works + let date = createDate(year: 2024, month: 3, day: 15, hour: 14, minute: 30, second: 45) + let result = DateDisplayFormatter.fullDateTime(date) + + // Verify it contains all expected components + XCTAssertTrue(result.contains("March")) + XCTAssertTrue(result.contains("15")) + XCTAssertTrue(result.contains("2024")) + XCTAssertTrue(result.contains("Friday")) + XCTAssertTrue(result.contains("14:30:45")) + } + + private func createDate(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> Date { + var components = DateComponents() + components.year = year + components.month = month + components.day = day + components.hour = hour + components.minute = minute + components.second = second + components.timeZone = TimeZone.current + return Calendar.current.date(from: components)! + } +} diff --git a/vBoxTests/OBDTypesTests.swift b/vBoxTests/OBDTypesTests.swift new file mode 100644 index 0000000..a97073e --- /dev/null +++ b/vBoxTests/OBDTypesTests.swift @@ -0,0 +1,555 @@ +// +// OBDTypesTests.swift +// vBoxTests +// +// Tests for OBD-II protocol types +// + +import XCTest +@testable import vBox + +final class OBDPIDTests: XCTestCase { + + // MARK: - PID Raw Values + + func testSpeedPIDRawValue() { + XCTAssertEqual(OBDPID.speed.rawValue, 0x10D) + } + + func testRPMPIDRawValue() { + XCTAssertEqual(OBDPID.rpm.rawValue, 0x10C) + } + + func testFuelLevelPIDRawValue() { + XCTAssertEqual(OBDPID.fuelLevel.rawValue, 0x12F) + } + + func testCoolantTempPIDRawValue() { + XCTAssertEqual(OBDPID.coolantTemp.rawValue, 0x105) + } + + func testEngineLoadPIDRawValue() { + XCTAssertEqual(OBDPID.engineLoad.rawValue, 0x104) + } + + func testThrottlePIDRawValue() { + XCTAssertEqual(OBDPID.throttle.rawValue, 0x111) + } + + // MARK: - PID Display Names + + func testPIDDisplayNames() { + XCTAssertEqual(OBDPID.speed.displayName, "Speed") + XCTAssertEqual(OBDPID.rpm.displayName, "RPM") + XCTAssertEqual(OBDPID.fuelLevel.displayName, "Fuel") + XCTAssertEqual(OBDPID.coolantTemp.displayName, "Coolant Temp") + XCTAssertEqual(OBDPID.engineLoad.displayName, "Engine Load") + XCTAssertEqual(OBDPID.throttle.displayName, "Throttle") + XCTAssertEqual(OBDPID.intakeTemp.displayName, "Intake Temp") + XCTAssertEqual(OBDPID.ambientTemp.displayName, "Ambient Temp") + XCTAssertEqual(OBDPID.barometric.displayName, "Barometric") + XCTAssertEqual(OBDPID.distance.displayName, "Distance") + } + + // MARK: - Max Valid Values + + func testMaxValidValues() { + XCTAssertEqual(OBDPID.speed.maxValidValue, 1000.0) + XCTAssertEqual(OBDPID.rpm.maxValidValue, 100000.0) + XCTAssertEqual(OBDPID.fuelLevel.maxValidValue, 150.0) + XCTAssertEqual(OBDPID.engineLoad.maxValidValue, 150.0) + XCTAssertEqual(OBDPID.coolantTemp.maxValidValue, 500.0) + } +} + +// MARK: - BLE Data Packet Tests + +final class BLEDataPacketTests: XCTestCase { + + // MARK: - Checksum Tests + + func testCalculateChecksumAllZeros() { + let data = Data([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + let checksum = BLEDataPacket.calculateChecksum(data: data, length: 12) + XCTAssertEqual(checksum, 0) + } + + func testCalculateChecksumSingleByte() { + let data = Data([0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + let checksum = BLEDataPacket.calculateChecksum(data: data, length: 12) + XCTAssertEqual(checksum, 0xFF) + } + + func testCalculateChecksumXORPattern() { + // XOR of 0xAA and 0x55 should be 0xFF + let data = Data([0xAA, 0x55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + let checksum = BLEDataPacket.calculateChecksum(data: data, length: 12) + XCTAssertEqual(checksum, 0xFF) + } + + func testCalculateChecksumSelfCanceling() { + // XOR of same value twice should be 0 + let data = Data([0xAB, 0xAB, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + let checksum = BLEDataPacket.calculateChecksum(data: data, length: 12) + XCTAssertEqual(checksum, 0) + } + + func testValidateChecksumValid() { + // Create data where XOR of all bytes = 0 (valid checksum) + var data = Data([0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + // Calculate what the last byte should be to make checksum = 0 + let partialChecksum = BLEDataPacket.calculateChecksum(data: data, length: 11) + data[11] = partialChecksum + XCTAssertTrue(BLEDataPacket.validateChecksum(data: data, length: 12)) + } + + func testValidateChecksumInvalid() { + // Data that doesn't XOR to 0 + let data = Data([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C]) + XCTAssertFalse(BLEDataPacket.validateChecksum(data: data, length: 12)) + } + + // MARK: - Packet Parsing Tests + + func testPacketTooShortReturnsNil() { + let data = Data([0x01, 0x02, 0x03, 0x04]) // Only 4 bytes + let packet = BLEDataPacket(data: data) + XCTAssertNil(packet) + } + + func testPacketInvalidChecksumReturnsNil() { + // 12 bytes but invalid checksum + let data = Data([0x00, 0x00, 0x00, 0x00, // time + 0x0D, 0x01, // pid (speed) + 0x00, // flags + 0xFF, // bad checksum + 0x00, 0x00, 0x00, 0x00]) // value + let packet = BLEDataPacket(data: data) + XCTAssertNil(packet) + } + + func testPacketConstantsAreCorrect() { + XCTAssertEqual(BLEDataPacket.legacyPacketSize, 12) + } + + // MARK: - Direct Initialization Tests + + func testDirectInitialization() { + let packet = BLEDataPacket( + time: 12345, + pid: OBDPID.speed.rawValue, + flags: 0, + checksum: 0, + values: (65.0, 0, 0) + ) + + XCTAssertEqual(packet.time, 12345) + XCTAssertEqual(packet.pid, OBDPID.speed.rawValue) + XCTAssertEqual(packet.obdPID, .speed) + XCTAssertEqual(packet.primaryValue, 65.0) + } + + func testOBDPIDMapping() { + let speedPacket = BLEDataPacket(time: 0, pid: 0x10D, flags: 0, checksum: 0, values: (0, 0, 0)) + XCTAssertEqual(speedPacket.obdPID, .speed) + + let rpmPacket = BLEDataPacket(time: 0, pid: 0x10C, flags: 0, checksum: 0, values: (0, 0, 0)) + XCTAssertEqual(rpmPacket.obdPID, .rpm) + + let unknownPacket = BLEDataPacket(time: 0, pid: 0xFFFF, flags: 0, checksum: 0, values: (0, 0, 0)) + XCTAssertNil(unknownPacket.obdPID) + } +} + +// MARK: - Diagnostic Reading Tests + +final class DiagnosticReadingTests: XCTestCase { + + // MARK: - Initialization + + func testInitialization() { + let reading = DiagnosticReading(pid: .speed, value: 65.0) + + XCTAssertEqual(reading.pid, .speed) + XCTAssertEqual(reading.value, 65.0) + XCTAssertNotNil(reading.timestamp) + } + + func testInitializationWithTimestamp() { + let date = Date(timeIntervalSince1970: 1000) + let reading = DiagnosticReading(pid: .rpm, value: 3000, timestamp: date) + + XCTAssertEqual(reading.pid, .rpm) + XCTAssertEqual(reading.value, 3000) + XCTAssertEqual(reading.timestamp, date) + } + + // MARK: - Validation + + func testValidSpeedReading() { + let reading = DiagnosticReading(pid: .speed, value: 100.0) + XCTAssertTrue(reading.isValid) + } + + func testInvalidNegativeValue() { + let reading = DiagnosticReading(pid: .speed, value: -10.0) + XCTAssertFalse(reading.isValid) + } + + func testInvalidExceedsMax() { + let reading = DiagnosticReading(pid: .speed, value: 2000.0) // max is 1000 + XCTAssertFalse(reading.isValid) + } + + func testValidAtMaxValue() { + let reading = DiagnosticReading(pid: .speed, value: 1000.0) + XCTAssertTrue(reading.isValid) + } + + func testValidAtZero() { + let reading = DiagnosticReading(pid: .fuelLevel, value: 0.0) + XCTAssertTrue(reading.isValid) + } + + // MARK: - Display String + + func testSpeedDisplayString() { + let reading = DiagnosticReading(pid: .speed, value: 65.0) + XCTAssertEqual(reading.displayString, "65 km/h") + } + + func testRPMDisplayString() { + let reading = DiagnosticReading(pid: .rpm, value: 3000.0) + XCTAssertEqual(reading.displayString, "3000 RPM") + } + + func testFuelLevelDisplayString() { + let reading = DiagnosticReading(pid: .fuelLevel, value: 75.0) + XCTAssertEqual(reading.displayString, "75%") + } + + func testCoolantTempDisplayString() { + let reading = DiagnosticReading(pid: .coolantTemp, value: 90.0) + XCTAssertEqual(reading.displayString, "90\u{00B0}C") + } + + func testIntakeTempDisplayString() { + let reading = DiagnosticReading(pid: .intakeTemp, value: 35.0) + XCTAssertEqual(reading.displayString, "35\u{00B0}C") + } + + func testAmbientTempDisplayString() { + let reading = DiagnosticReading(pid: .ambientTemp, value: 25.0) + XCTAssertEqual(reading.displayString, "25\u{00B0}C") + } + + func testEngineLoadDisplayString() { + let reading = DiagnosticReading(pid: .engineLoad, value: 45.5) + XCTAssertEqual(reading.displayString, "45.5%") + } + + func testThrottleDisplayString() { + let reading = DiagnosticReading(pid: .throttle, value: 30.3) + XCTAssertEqual(reading.displayString, "30.3%") + } + + func testBarometricDisplayString() { + let reading = DiagnosticReading(pid: .barometric, value: 101.0) + XCTAssertEqual(reading.displayString, "101 kPa") + } + + func testDistanceDisplayString() { + let reading = DiagnosticReading(pid: .distance, value: 150.5) + XCTAssertEqual(reading.displayString, "150.5 km") + } +} + +// MARK: - Vehicle Diagnostics Tests + +final class VehicleDiagnosticsTests: XCTestCase { + + // MARK: - Initialization + + func testEmptyInitialization() { + let diagnostics = VehicleDiagnostics() + + XCTAssertNil(diagnostics.speed) + XCTAssertNil(diagnostics.rpm) + XCTAssertNil(diagnostics.fuelLevel) + XCTAssertNil(diagnostics.coolantTemp) + XCTAssertNil(diagnostics.engineLoad) + XCTAssertNil(diagnostics.throttle) + XCTAssertNil(diagnostics.intakeTemp) + XCTAssertNil(diagnostics.ambientTemp) + XCTAssertNil(diagnostics.barometric) + XCTAssertNil(diagnostics.distance) + } + + // MARK: - Update with Reading + + func testUpdateSpeed() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .speed, value: 65.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.speed, 65.0) + } + + func testUpdateRPM() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .rpm, value: 3000.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.rpm, 3000.0) + } + + func testUpdateFuelLevel() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .fuelLevel, value: 75.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.fuelLevel, 75.0) + } + + func testUpdateCoolantTemp() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .coolantTemp, value: 90.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.coolantTemp, 90.0) + } + + func testUpdateEngineLoad() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .engineLoad, value: 45.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.engineLoad, 45.0) + } + + func testUpdateThrottle() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .throttle, value: 30.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.throttle, 30.0) + } + + func testUpdateIntakeTemp() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .intakeTemp, value: 35.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.intakeTemp, 35.0) + } + + func testUpdateAmbientTemp() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .ambientTemp, value: 25.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.ambientTemp, 25.0) + } + + func testUpdateBarometric() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .barometric, value: 101.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.barometric, 101.0) + } + + func testUpdateDistance() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .distance, value: 150.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.distance, 150.0) + } + + func testUpdateRuntime() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .runtime, value: 3600.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.runtime, 3600.0) + } + + func testUpdateEngineFuelRate() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .engineFuelRate, value: 5.5) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.engineFuelRate, 5.5) + } + + func testUpdateEngineTorquePercentage() { + var diagnostics = VehicleDiagnostics() + let reading = DiagnosticReading(pid: .engineTorquePercentage, value: 80.0) + + diagnostics.update(with: reading) + + XCTAssertEqual(diagnostics.engineTorquePercentage, 80.0) + } + + // MARK: - Invalid Reading Handling + + func testInvalidReadingIgnored() { + var diagnostics = VehicleDiagnostics() + let invalidReading = DiagnosticReading(pid: .speed, value: -10.0) + + diagnostics.update(with: invalidReading) + + XCTAssertNil(diagnostics.speed) + } + + func testExceedsMaxValueIgnored() { + var diagnostics = VehicleDiagnostics() + let invalidReading = DiagnosticReading(pid: .speed, value: 2000.0) + + diagnostics.update(with: invalidReading) + + XCTAssertNil(diagnostics.speed) + } + + // MARK: - Update with Packet + + func testUpdateWithPacket() { + var diagnostics = VehicleDiagnostics() + let packet = BLEDataPacket( + time: 12345, + pid: OBDPID.speed.rawValue, + flags: 0, + checksum: 0, + values: (65.0, 0, 0) + ) + + diagnostics.update(with: packet) + + XCTAssertEqual(diagnostics.speed, 65.0) + } + + func testUpdateWithUnknownPIDPacketIgnored() { + var diagnostics = VehicleDiagnostics() + let packet = BLEDataPacket( + time: 12345, + pid: 0xFFFF, // Unknown PID + flags: 0, + checksum: 0, + values: (65.0, 0, 0) + ) + + diagnostics.update(with: packet) + + // All values should still be nil + XCTAssertNil(diagnostics.speed) + XCTAssertNil(diagnostics.rpm) + } + + // MARK: - Timestamp Updates + + func testTimestampUpdatesOnValidReading() { + var diagnostics = VehicleDiagnostics() + let initialTime = diagnostics.lastUpdated + + // Small delay to ensure time difference + Thread.sleep(forTimeInterval: 0.01) + + let reading = DiagnosticReading(pid: .speed, value: 65.0) + diagnostics.update(with: reading) + + XCTAssertGreaterThan(diagnostics.lastUpdated, initialTime) + } + + func testTimestampDoesNotUpdateOnInvalidReading() { + var diagnostics = VehicleDiagnostics() + let initialTime = diagnostics.lastUpdated + + let invalidReading = DiagnosticReading(pid: .speed, value: -10.0) + diagnostics.update(with: invalidReading) + + XCTAssertEqual(diagnostics.lastUpdated, initialTime) + } + + // MARK: - Multiple Updates + + func testMultipleUpdates() { + var diagnostics = VehicleDiagnostics() + + diagnostics.update(with: DiagnosticReading(pid: .speed, value: 60.0)) + diagnostics.update(with: DiagnosticReading(pid: .rpm, value: 2500.0)) + diagnostics.update(with: DiagnosticReading(pid: .fuelLevel, value: 80.0)) + diagnostics.update(with: DiagnosticReading(pid: .coolantTemp, value: 85.0)) + + XCTAssertEqual(diagnostics.speed, 60.0) + XCTAssertEqual(diagnostics.rpm, 2500.0) + XCTAssertEqual(diagnostics.fuelLevel, 80.0) + XCTAssertEqual(diagnostics.coolantTemp, 85.0) + } + + func testOverwritePreviousValue() { + var diagnostics = VehicleDiagnostics() + + diagnostics.update(with: DiagnosticReading(pid: .speed, value: 60.0)) + XCTAssertEqual(diagnostics.speed, 60.0) + + diagnostics.update(with: DiagnosticReading(pid: .speed, value: 70.0)) + XCTAssertEqual(diagnostics.speed, 70.0) + } +} + +// MARK: - Accelerometer Reading Tests + +final class AccelerometerReadingTests: XCTestCase { + + func testInitialization() { + let reading = AccelerometerReading(x: 0.1, y: 0.2, z: 0.3) + + XCTAssertEqual(reading.x, 0.1) + XCTAssertEqual(reading.y, 0.2) + XCTAssertEqual(reading.z, 0.3) + XCTAssertNotNil(reading.timestamp) + } + + func testInitializationWithTimestamp() { + let date = Date(timeIntervalSince1970: 1000) + let reading = AccelerometerReading(x: 1.0, y: 0.0, z: 0.0, timestamp: date) + + XCTAssertEqual(reading.timestamp, date) + } + + func testMagnitudeSimple() { + // (3, 4, 0) should have magnitude 5 + let reading = AccelerometerReading(x: 3.0, y: 4.0, z: 0.0) + XCTAssertEqual(reading.magnitude, 5.0, accuracy: 0.001) + } + + func testMagnitude3D() { + // (1, 2, 2) should have magnitude 3 + let reading = AccelerometerReading(x: 1.0, y: 2.0, z: 2.0) + XCTAssertEqual(reading.magnitude, 3.0, accuracy: 0.001) + } + + func testMagnitudeZero() { + let reading = AccelerometerReading(x: 0.0, y: 0.0, z: 0.0) + XCTAssertEqual(reading.magnitude, 0.0) + } + + func testMagnitudeWithNegatives() { + // (-3, -4, 0) should also have magnitude 5 + let reading = AccelerometerReading(x: -3.0, y: -4.0, z: 0.0) + XCTAssertEqual(reading.magnitude, 5.0, accuracy: 0.001) + } +} diff --git a/vBoxTests/TestHelpers.swift b/vBoxTests/TestHelpers.swift new file mode 100644 index 0000000..6c75b99 --- /dev/null +++ b/vBoxTests/TestHelpers.swift @@ -0,0 +1,259 @@ +// +// TestHelpers.swift +// vBoxTests +// +// Common test utilities and helpers +// + +import XCTest +import CoreLocation +@testable import vBox + +// MARK: - Test Data Builders + +/// Helper for creating test BLE data packets +enum TestBLEDataBuilder { + + /// Create valid BLE data with correct checksum + static func createValidPacket( + time: UInt32 = 0, + pid: UInt16, + flags: UInt8 = 0, + value: Float + ) -> Data { + var data = Data(count: 12) + + // Write time (4 bytes) + withUnsafeBytes(of: time) { data.replaceSubrange(0..<4, with: $0) } + + // Write PID (2 bytes) + withUnsafeBytes(of: pid) { data.replaceSubrange(4..<6, with: $0) } + + // Write flags (1 byte) + data[6] = flags + + // Write value (4 bytes starting at offset 8) + withUnsafeBytes(of: value) { data.replaceSubrange(8..<12, with: $0) } + + // Calculate checksum for first 11 bytes and store at index 7 + var checksum: UInt8 = 0 + for i in 0..<7 { + checksum ^= data[i] + } + for i in 8..<12 { + checksum ^= data[i] + } + data[7] = checksum + + return data + } + + /// Create a speed reading packet + static func speedPacket(value: Float) -> Data { + return createValidPacket(pid: OBDPID.speed.rawValue, value: value) + } + + /// Create an RPM reading packet + static func rpmPacket(value: Float) -> Data { + return createValidPacket(pid: OBDPID.rpm.rawValue, value: value) + } + + /// Create a fuel level reading packet + static func fuelPacket(value: Float) -> Data { + return createValidPacket(pid: OBDPID.fuelLevel.rawValue, value: value) + } +} + +// MARK: - Test Location Helpers + +/// Helper for creating test locations +enum TestLocationBuilder { + + /// San Francisco coordinates + static let sanFrancisco = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) + + /// Los Angeles coordinates + static let losAngeles = CLLocationCoordinate2D(latitude: 34.0522, longitude: -118.2437) + + /// New York coordinates + static let newYork = CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060) + + /// Create a CLLocation with the given parameters + static func location( + latitude: Double, + longitude: Double, + altitude: Double = 0, + speed: Double = 0, + timestamp: Date = Date() + ) -> CLLocation { + return CLLocation( + coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), + altitude: altitude, + horizontalAccuracy: 10, + verticalAccuracy: 10, + course: 0, + speed: speed, + timestamp: timestamp + ) + } + + /// Create a path of locations simulating a trip + static func tripPath( + from start: CLLocationCoordinate2D, + to end: CLLocationCoordinate2D, + pointCount: Int = 10, + duration: TimeInterval = 3600 + ) -> [CLLocation] { + var locations: [CLLocation] = [] + let startTime = Date(timeIntervalSinceNow: -duration) + + for i in 0.. VehicleDiagnostics { + var diagnostics = VehicleDiagnostics() + diagnostics.update(with: DiagnosticReading(pid: .speed, value: 0)) + diagnostics.update(with: DiagnosticReading(pid: .rpm, value: 800)) + diagnostics.update(with: DiagnosticReading(pid: .fuelLevel, value: 75)) + diagnostics.update(with: DiagnosticReading(pid: .coolantTemp, value: 85)) + diagnostics.update(with: DiagnosticReading(pid: .engineLoad, value: 15)) + return diagnostics + } + + /// Create a VehicleDiagnostics with typical highway driving values + static func highwayDiagnostics() -> VehicleDiagnostics { + var diagnostics = VehicleDiagnostics() + diagnostics.update(with: DiagnosticReading(pid: .speed, value: 120)) + diagnostics.update(with: DiagnosticReading(pid: .rpm, value: 3500)) + diagnostics.update(with: DiagnosticReading(pid: .fuelLevel, value: 60)) + diagnostics.update(with: DiagnosticReading(pid: .coolantTemp, value: 90)) + diagnostics.update(with: DiagnosticReading(pid: .engineLoad, value: 45)) + diagnostics.update(with: DiagnosticReading(pid: .throttle, value: 30)) + return diagnostics + } + + /// Create a sequence of diagnostic readings simulating acceleration + static func accelerationSequence( + fromSpeed startSpeed: Float = 0, + toSpeed endSpeed: Float = 100, + duration: TimeInterval = 10, + readings: Int = 10 + ) -> [DiagnosticReading] { + var result: [DiagnosticReading] = [] + let startTime = Date() + + for i in 0.. Bool, + timeout: TimeInterval = 5.0, + pollInterval: TimeInterval = 0.1, + description: String = "Condition" + ) { + let expectation = XCTestExpectation(description: description) + + let startTime = Date() + while !condition() { + if Date().timeIntervalSince(startTime) > timeout { + XCTFail("\(description) timed out after \(timeout) seconds") + return + } + RunLoop.current.run(until: Date(timeIntervalSinceNow: pollInterval)) + } + + expectation.fulfill() + } +} + +// MARK: - Test Date Helpers + +extension Date { + /// Create a date for testing with specific components + static func testDate( + year: Int = 2024, + month: Int = 1, + day: Int = 1, + hour: Int = 12, + minute: Int = 0, + second: Int = 0 + ) -> Date { + var components = DateComponents() + components.year = year + components.month = month + components.day = day + components.hour = hour + components.minute = minute + components.second = second + components.timeZone = TimeZone(identifier: "UTC") + return Calendar.current.date(from: components)! + } +} + +// MARK: - Comparison Helpers + +/// Floating point comparison with tolerance +func assertAlmostEqual( + _ actual: Float, + _ expected: Float, + tolerance: Float = 0.001, + file: StaticString = #file, + line: UInt = #line +) { + XCTAssertEqual(actual, expected, accuracy: tolerance, file: file, line: line) +} + +func assertAlmostEqual( + _ actual: Double, + _ expected: Double, + tolerance: Double = 0.001, + file: StaticString = #file, + line: UInt = #line +) { + XCTAssertEqual(actual, expected, accuracy: tolerance, file: file, line: line) +} diff --git a/vBoxTests/vBoxTests-Bridging-Header.h b/vBoxTests/vBoxTests-Bridging-Header.h new file mode 100644 index 0000000..1842081 --- /dev/null +++ b/vBoxTests/vBoxTests-Bridging-Header.h @@ -0,0 +1,37 @@ +// +// vBoxTests-Bridging-Header.h +// vBoxTests +// +// Bridging header for test target to access Objective-C code +// + +#ifndef vBoxTests_Bridging_Header_h +#define vBoxTests_Bridging_Header_h + +// Core Data Models +#import "DrivingHistory.h" +#import "Trip.h" +#import "GPSLocation.h" +#import "BluetoothData.h" + +// Managers +#import "BLEManager.h" + +// View Controllers +#import "AppDelegate.h" +#import "MainScreenViewController.h" +#import "GoogleMapsViewController.h" +#import "TripDetailViewController.h" +#import "DrivingHistoryViewController.h" +#import "BluetoothTableViewController.h" +#import "DebugBluetoothViewController.h" + +// Utilities +#import "UtilityMethods.h" + +// Third-party +#import "WMGaugeView.h" +#import "OBSlider.h" +#import "SVProgressHUD.h" + +#endif /* vBoxTests_Bridging_Header_h */