diff --git a/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj b/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj index b993f6cd1..472809574 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj +++ b/Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj @@ -274,9 +274,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43CABE071C3506F100005705 /* Build configuration list for PBXNativeTarget "CGMBLEKit" */; buildPhases = ( + 43CABDF01C3506F100005705 /* Headers */, 43CABDEE1C3506F100005705 /* Sources */, 43CABDEF1C3506F100005705 /* Frameworks */, - 43CABDF01C3506F100005705 /* Headers */, 43CABDF11C3506F100005705 /* Resources */, ); buildRules = ( diff --git a/Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift b/Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift index dd9dfdcb3..1999fd3b3 100644 --- a/Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift +++ b/Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift @@ -22,7 +22,7 @@ struct SignalStrengthIndicator: View { var totalBars: Int = 5 var body: some View { HStack { - ForEach(0.. + + + + AvailableLibraries + + + LibraryIdentifier + ios-armv7_arm64 + LibraryPath + ConnectIQ.framework + SupportedArchitectures + + armv7 + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-i386_x86_64-simulator + LibraryPath + ConnectIQ.framework + SupportedArchitectures + + i386 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ConnectIQ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ConnectIQ new file mode 100755 index 000000000..2874b6d22 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ConnectIQ differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/ConnectIQ.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/ConnectIQ.h new file mode 100644 index 000000000..632ae87e7 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/ConnectIQ.h @@ -0,0 +1,237 @@ +// +// ConnectIQ.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import +#import "IQConstants.h" +#import "IQDevice.h" +#import "IQApp.h" + +// -------------------------------------------------------------------------------- +#pragma mark - PUBLIC TYPES +// -------------------------------------------------------------------------------- + +/// @brief SendMessage progress callback block +/// +/// @param sentBytes The number of bytes that have been successfully transferred +/// to the device so far for this connection. +/// @param totalBytes The total number of bytes to transfer for this connection. +typedef void (^IQSendMessageProgress)(uint32_t sentBytes, uint32_t totalBytes); + +/// @brief SendMessage completion callback block +/// +/// @param result The result of the SendMessage operation. +typedef void (^IQSendMessageCompletion)(IQSendMessageResult result); + +/// @brief Conforming to the IQUIOverrideDelegate protocol indicates that an +/// object handles one or more events triggered by the ConnectIQ SDK that +/// require user input. +@protocol IQUIOverrideDelegate +@optional +/// @brief Called by the ConnectIQ SDK when an action has been requested that +/// requires Garmin Connect Mobile to be installed. +/// +/// The receiver should choose whether or not to launch the Apple App +/// Store page for GCM, ideally by presenting the user with a choice. +/// +/// If the receiver of this message decides to install GCM, it must call +/// showAppStoreForConnectMobile. +- (void)needsToInstallConnectMobile; +@end + +/// @brief Conforming to the IQDeviceEventDelegate protocol indicates that an +/// object handles ConnectIQ device status events. +@protocol IQDeviceEventDelegate +@optional +/// @brief Called by the ConnectIQ SDK when an IQDevice's connection status has +/// changed. +/// +/// @param device The IQDevice whose status changed. +/// @param status The new status of the device. +- (void)deviceStatusChanged:(IQDevice *)device status:(IQDeviceStatus)status; +@end + +/// @brief Conforming to the IQAppMessageDelegate protocol indicates that an +/// object handles messages from ConnectIQ apps on compatible devices. +@protocol IQAppMessageDelegate +@optional +/// @brief Called by the ConnectIQ SDK when a message is received from a device. +/// +/// @param message The message that was received. +/// @param app The device app that sent the message. +- (void)receivedMessage:(id)message fromApp:(IQApp *)app; +@end + +// -------------------------------------------------------------------------------- +#pragma mark - CLASS DEFINITION +// -------------------------------------------------------------------------------- + +/// @brief The root of the ConnectIQ SDK API. +@interface ConnectIQ : NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +// -------------------------------------------------------------------------------- +#pragma mark - SINGLETON ACCESS +// -------------------------------------------------------------------------------- + +/// @brief Exposes the single static instance of the ConnectIQ class. +/// +/// @return The single status instance of the ConnectIQ class. ++ (ConnectIQ *)sharedInstance; + +// -------------------------------------------------------------------------------- +#pragma mark - INITIALIZATION +// -------------------------------------------------------------------------------- + +/// @brief Initializes the ConnectIQ SDK with startup parameters necessary for +/// its operation. +/// +/// @param urlScheme The URL scheme for this companion app. When Garmin Connect +/// Mobile is launched, it will return to the companion app by +/// launching a URL with this scheme. +/// @param delegate The delegate that the SDK will use for notifying the +/// companion app about events that require user input. If this +/// is nil, the SDK's default UI will be used. +- (void)initializeWithUrlScheme:(NSString *)urlScheme uiOverrideDelegate:(id)delegate; + +// -------------------------------------------------------------------------------- +#pragma mark - EXTERNAL LAUNCHING +// -------------------------------------------------------------------------------- + +/// @brief Launches the Apple App Store page for the Garmin Connect Mobile app. +/// The companion app should only call this in response to a +/// needsToInstallConnectMobile event that gets triggered on the +/// IQUIOverrideDelegate. +- (void)showAppStoreForConnectMobile; + +/// @brief Launches Garmin Connect Mobile for the purpose of retrieving a list of +/// ConnectIQ-compatible devices. +/// +/// Once the user has chosen which ConnectIQ devices to share with the +/// companion app, GCM will return those devices to the companion app by +/// opening a URL with the scheme registered in +/// initializeWithUrlScheme:uiOverrideDelegate:. +/// +/// The companion app should handle this URL by passing it in to the +/// parseDeviceSelectionResponseFromURL: method to get the list of devices +/// that the user permitted the companion app to communicate with. +- (void)showConnectIQDeviceSelection; + +/// @brief Parses a URL opened from Garmin Connect Mobile into a list of devices. +/// +/// @param url The URL to parse. +/// +/// @return An array of IQDevice objects representing the ConnectIQ-compatible +/// devices that the user allowed GCM to share with the companion app. +/// +/// @seealso showConnectIQDeviceSelection +- (NSArray *)parseDeviceSelectionResponseFromURL:(NSURL *)url; + +/// @brief Launches Garmin Connect Mobile and shows the ConnectIQ app store page +/// for the given app. +/// +/// The companion app should call this if the user would like to manage +/// the app on the device, such as to install, upgrade, uninstall, or +/// modify settings. +/// +/// @param app The app to show the ConnectIQ app store page for. +- (void)showConnectIQStoreForApp:(IQApp *)app; + +// -------------------------------------------------------------------------------- +#pragma mark - DEVICE MANAGEMENT +// -------------------------------------------------------------------------------- + +/// @brief Registers an object as a listener for ConnectIQ device status events. +/// +/// A device may have multiple device event listeners if this method is +/// called more than once. +/// +/// @param device A device to listen for status events from. +/// @param delegate The listener which will receive status events for this device. +- (void)registerForDeviceEvents:(IQDevice *)device delegate:(id)delegate; + +/// @brief Unregisters a listener for a specific device. +/// +/// @param device The device to unregister the listener for. +/// @param delegate The listener to remove from the device. +- (void)unregisterForDeviceEvents:(IQDevice *)device delegate:(id)delegate; + +/// @brief Unregisters the specified listener from all devices for which it had +/// previously been registered. +/// +/// @param delegate The listener to unregister. +- (void)unregisterForAllDeviceEvents:(id)delegate; + +/// @brief Gets the current connection status of a device. +/// +/// The device must have been registered for event notifications by +/// calling registerForDeviceEvents:delegate: or this method will return +/// IQDeviceStatus_InvalidDevice. +/// +/// @param device The device to get the status for. +/// +/// @return The device's current connection status. +- (IQDeviceStatus)getDeviceStatus:(IQDevice *)device; + +// -------------------------------------------------------------------------------- +#pragma mark - APP MANAGEMENT +// -------------------------------------------------------------------------------- + +/// @brief Begins getting the status of an app on a device. This method returns +/// immediately. +/// +/// @param app The IQApp to get the status for. +/// @param completion The completion block that will be triggered when the device +/// status operation is complete. +- (void)getAppStatus:(IQApp *)app completion:(void(^)(IQAppStatus *appStatus))completion; + +/// @brief Registers an object as a listener for ConnectIQ messages from an app +/// on a device. +/// +/// An app may have multiple message listeners if this method is called +/// more than once. +/// +/// @param app The app to listen for messages from. +/// @param delegate The listener which will receive messages for this app. +- (void)registerForAppMessages:(IQApp *)app delegate:(id)delegate; + +/// @brief Unregisters a listener for a specific app. +/// +/// @param app The app to unregister a listener for. +/// @param delegate The listener to remove from the app. +- (void)unregisterForAppMessages:(IQApp *)app delegate:(id)delegate; + +/// @brief Unregisters all previously registered apps for a specific listener. +/// +/// @param delegate The listener to unregister. +- (void)unregisterForAllAppMessages:(id)delegate; + +/// @brief Begins sending a message to an app. This method returns immediately. +/// +/// @param message The message to send to the app. This message must be one of +/// the following types: NSString, NSNumber, NSNull, NSArray, +/// or NSDictionary. Arrays and dictionaries may be nested. +/// @param app The app to send the message to. +/// @param progress A progress block that will be triggered periodically +/// throughout the transfer. This is guaranteed to be triggered +/// at least once. +/// @param completion A completion block that will be triggered when the send +/// message operation is complete. +- (void)sendMessage:(id)message toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion; + +/// @brief Sends an open app request message request to the device. This method returns immediately. +/// +/// @param app The app to open. +/// @param completion A completion block that will be triggered when the send +/// message operation is complete. +- (void)openAppRequest:(IQApp *)app completion:(IQSendMessageCompletion)completion; + +// TODO *** Holding off on documenting this until this method actually works. +- (void)sendImage:(NSData *)bitmap toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQApp.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQApp.h new file mode 100644 index 000000000..a9dfe8c54 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQApp.h @@ -0,0 +1,34 @@ +// +// IQApp.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import +#import "IQDevice.h" +#import "IQAppStatus.h" + +/// @brief Represents an instance of a ConnectIQ app that is installed on a +/// Garmin device. +@interface IQApp : NSObject + +/// @brief The unique identifier for this app. +@property (nonatomic, readonly) NSUUID *uuid; + +/// @brief The unique identifier for this app in the store. +@property (nonatomic, readonly) NSUUID *storeUuid; + +/// @brief The device that this app is installed on. +@property (nonatomic, readonly) IQDevice *device; + +/// @brief Creates a new app instance. +/// +/// @param uuid The UUID of the app to create. +/// @param storeUuid The store UUID of the app to create. +/// @param device The device the app to create is installed on. +/// +/// @return A new IQApp instance with the appropriate values set. ++ (IQApp *)appWithUUID:(NSUUID *)uuid storeUuid:(NSUUID *)storeUuid device:(IQDevice *)device; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQAppStatus.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQAppStatus.h new file mode 100644 index 000000000..663641d15 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQAppStatus.h @@ -0,0 +1,20 @@ +// +// IQAppStatus.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import + +/// @brief Represents the current status of an app on a Garmin device. +@interface IQAppStatus : NSObject + +/// @brief YES if the app is installed on the device, NO if it isn't. +@property (nonatomic, readonly) BOOL isInstalled; + +/// @brief The version of the app that is currently installed on the device. If +/// the app is not installed, this value is unused. +@property (nonatomic, readonly) uint16_t version; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQConstants.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQConstants.h new file mode 100644 index 000000000..b9017759a --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQConstants.h @@ -0,0 +1,63 @@ +// +// IQConstants.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import + +/// @brief The current version of the ConnectIQ SDK. +extern int const IQSDKVersion; + +/// @brief The bundle identifier for the Garmin Connect Mobile app. +extern NSString * const IQGCMBundle; + +/// @brief The result of a SendMessage operation +typedef NS_ENUM(NSInteger, IQSendMessageResult){ + ///! @brief The message was sent successfully. + IQSendMessageResult_Success, + + /// @brief The message failed to send due to an unknown error. + IQSendMessageResult_Failure_Unknown, + + /// @brief The message failed to send. There was an error within the SDK or + /// on the device. + IQSendMessageResult_Failure_InternalError, + + /// @brief The message failed to send. The device is not available right now. + IQSendMessageResult_Failure_DeviceNotAvailable, + + /// @brief The message failed to send. The app is not installed on the + /// device. + IQSendMessageResult_Failure_AppNotFound, + + /// @brief The message failed to send. The device is busy and cannot receive + /// the message right now. + IQSendMessageResult_Failure_DeviceIsBusy, + + /// @brief The message failed to send. The message contained an unsupported + /// type. + IQSendMessageResult_Failure_UnsupportedType, + + /// @brief The message failed to send. The device does not have enough memory + /// to receive the message. + IQSendMessageResult_Failure_InsufficientMemory, + + /// @brief The message failed to send. The connection timed out while sending + /// the message. + IQSendMessageResult_Failure_Timeout, + + /// @brief The message failed to send and was retried, but could not complete + /// after a number of tries. + IQSendMessageResult_Failure_MaxRetries, + + /// @brief The message was received by the device but it chose not to display + /// a message prompt, ignoring the message. + IQSendMessageResult_Failure_PromptNotDisplayed, + + /// @brief The message was received by the device but the app to open + /// was already running on the device. + IQSendMessageResult_Failure_AppAlreadyRunning, +}; +NSString *NSStringFromSendMessageResult(IQSendMessageResult value); diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQDevice.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQDevice.h new file mode 100644 index 000000000..6842829f7 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Headers/IQDevice.h @@ -0,0 +1,61 @@ +// +// IQDevice.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import +#import + +/// @brief The current status of an IQDevice. +typedef NS_ENUM(NSInteger, IQDeviceStatus){ + /// @brief No device with this UUID has been registered for status events + /// the SDK. + IQDeviceStatus_InvalidDevice, + + /// @brief Bluetooth is either powered off or resetting. + IQDeviceStatus_BluetoothNotReady, + + /// @brief This device could not be found by iOS. Perhaps the user removed + /// the device? + IQDeviceStatus_NotFound, + + /// @brief The device is recognized by iOS, but it is not currently + /// connected. + IQDeviceStatus_NotConnected, + + /// @brief The device is connected and ready to communicate. + IQDeviceStatus_Connected, +}; + +/// @brief Represents a ConnectIQ-compatible Garmin device. +@interface IQDevice : NSObject + +/// @brief The unique identifier for this device. +@property (nonatomic, readonly) NSUUID *uuid; + +/// @brief The model name of the device provided by Garmin Connect Mobile. +@property (nonatomic, readonly) NSString *modelName; + +/// @brief The friendly name of the device, set by the user and provided by +/// Garmin Connect Mobile. +@property (nonatomic, readonly) NSString *friendlyName; + +/// @brief Creates a new device instance. +/// +/// @param uuid The UUID of the device to create. +/// @param modelName The model name of the device to create. +/// @param friendlyName The friendly name of the device to create. +/// +/// @return A new IQDevice instance with the appropriate values set. ++ (IQDevice *)deviceWithId:(NSUUID *)uuid modelName:(NSString *)modelName friendlyName:(NSString *)friendlyName; + +/// @brief Creates a new device instance by copying another device's values. +/// +/// @param device The device to copy values from. +/// +/// @return A new IQDevice instance with all values copied. +- (instancetype)initWithDevice:(IQDevice *)device; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Info.plist b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Info.plist new file mode 100644 index 000000000..a15fe1075 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Info.plist differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Modules/module.modulemap b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Modules/module.modulemap new file mode 100644 index 000000000..685a0721e --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module ConnectIQ { + umbrella header "ConnectIQ.h" + + export * + module * { export * } +} diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ar.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ar.lproj/IQLocalizable.strings new file mode 100644 index 000000000..772c7c199 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ar.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/cs.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/cs.lproj/IQLocalizable.strings new file mode 100644 index 000000000..294594b65 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/cs.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/da.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/da.lproj/IQLocalizable.strings new file mode 100644 index 000000000..9c7faad3e Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/da.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/de.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/de.lproj/IQLocalizable.strings new file mode 100644 index 000000000..cb4d87b1b Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/de.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/el.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/el.lproj/IQLocalizable.strings new file mode 100644 index 000000000..8f4e27056 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/el.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/en.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/en.lproj/IQLocalizable.strings new file mode 100644 index 000000000..8794262a9 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/en.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/es.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/es.lproj/IQLocalizable.strings new file mode 100644 index 000000000..247b71ac7 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/es.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fi.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fi.lproj/IQLocalizable.strings new file mode 100644 index 000000000..344d06c1b Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fi.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fr.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fr.lproj/IQLocalizable.strings new file mode 100644 index 000000000..814e34f18 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/fr.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/he.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/he.lproj/IQLocalizable.strings new file mode 100644 index 000000000..b29a9b084 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/he.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hr.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hr.lproj/IQLocalizable.strings new file mode 100644 index 000000000..e10bd29af Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hr.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hu.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hu.lproj/IQLocalizable.strings new file mode 100644 index 000000000..9c96de1e8 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/hu.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/id.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/id.lproj/IQLocalizable.strings new file mode 100644 index 000000000..eda1bc3d3 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/id.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/it.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/it.lproj/IQLocalizable.strings new file mode 100644 index 000000000..1bd95ac77 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/it.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ja.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ja.lproj/IQLocalizable.strings new file mode 100644 index 000000000..0f5b121c0 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ja.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ko.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ko.lproj/IQLocalizable.strings new file mode 100644 index 000000000..3f3749b0e Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ko.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ms.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ms.lproj/IQLocalizable.strings new file mode 100644 index 000000000..499c806b2 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ms.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nb.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nb.lproj/IQLocalizable.strings new file mode 100644 index 000000000..436c9f67d Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nb.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nl.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nl.lproj/IQLocalizable.strings new file mode 100644 index 000000000..97d1c509c Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/nl.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pl.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pl.lproj/IQLocalizable.strings new file mode 100644 index 000000000..0f8f3d72b Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pl.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings new file mode 100644 index 000000000..6f9022878 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt.lproj/IQLocalizable.strings new file mode 100644 index 000000000..ddc7d69a1 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/pt.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ru.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ru.lproj/IQLocalizable.strings new file mode 100644 index 000000000..ce83c0a22 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/ru.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sk.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sk.lproj/IQLocalizable.strings new file mode 100644 index 000000000..98b954bff Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sk.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sv.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sv.lproj/IQLocalizable.strings new file mode 100644 index 000000000..316d6f897 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/sv.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/th.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/th.lproj/IQLocalizable.strings new file mode 100644 index 000000000..f98199499 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/th.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/tr.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/tr.lproj/IQLocalizable.strings new file mode 100644 index 000000000..c71eb4a00 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/tr.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings new file mode 100644 index 000000000..d630d3f0c Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings new file mode 100644 index 000000000..db0daa2c7 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-armv7_arm64/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ConnectIQ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ConnectIQ new file mode 100755 index 000000000..1a203c55b Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ConnectIQ differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/ConnectIQ.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/ConnectIQ.h new file mode 100644 index 000000000..632ae87e7 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/ConnectIQ.h @@ -0,0 +1,237 @@ +// +// ConnectIQ.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import +#import "IQConstants.h" +#import "IQDevice.h" +#import "IQApp.h" + +// -------------------------------------------------------------------------------- +#pragma mark - PUBLIC TYPES +// -------------------------------------------------------------------------------- + +/// @brief SendMessage progress callback block +/// +/// @param sentBytes The number of bytes that have been successfully transferred +/// to the device so far for this connection. +/// @param totalBytes The total number of bytes to transfer for this connection. +typedef void (^IQSendMessageProgress)(uint32_t sentBytes, uint32_t totalBytes); + +/// @brief SendMessage completion callback block +/// +/// @param result The result of the SendMessage operation. +typedef void (^IQSendMessageCompletion)(IQSendMessageResult result); + +/// @brief Conforming to the IQUIOverrideDelegate protocol indicates that an +/// object handles one or more events triggered by the ConnectIQ SDK that +/// require user input. +@protocol IQUIOverrideDelegate +@optional +/// @brief Called by the ConnectIQ SDK when an action has been requested that +/// requires Garmin Connect Mobile to be installed. +/// +/// The receiver should choose whether or not to launch the Apple App +/// Store page for GCM, ideally by presenting the user with a choice. +/// +/// If the receiver of this message decides to install GCM, it must call +/// showAppStoreForConnectMobile. +- (void)needsToInstallConnectMobile; +@end + +/// @brief Conforming to the IQDeviceEventDelegate protocol indicates that an +/// object handles ConnectIQ device status events. +@protocol IQDeviceEventDelegate +@optional +/// @brief Called by the ConnectIQ SDK when an IQDevice's connection status has +/// changed. +/// +/// @param device The IQDevice whose status changed. +/// @param status The new status of the device. +- (void)deviceStatusChanged:(IQDevice *)device status:(IQDeviceStatus)status; +@end + +/// @brief Conforming to the IQAppMessageDelegate protocol indicates that an +/// object handles messages from ConnectIQ apps on compatible devices. +@protocol IQAppMessageDelegate +@optional +/// @brief Called by the ConnectIQ SDK when a message is received from a device. +/// +/// @param message The message that was received. +/// @param app The device app that sent the message. +- (void)receivedMessage:(id)message fromApp:(IQApp *)app; +@end + +// -------------------------------------------------------------------------------- +#pragma mark - CLASS DEFINITION +// -------------------------------------------------------------------------------- + +/// @brief The root of the ConnectIQ SDK API. +@interface ConnectIQ : NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +// -------------------------------------------------------------------------------- +#pragma mark - SINGLETON ACCESS +// -------------------------------------------------------------------------------- + +/// @brief Exposes the single static instance of the ConnectIQ class. +/// +/// @return The single status instance of the ConnectIQ class. ++ (ConnectIQ *)sharedInstance; + +// -------------------------------------------------------------------------------- +#pragma mark - INITIALIZATION +// -------------------------------------------------------------------------------- + +/// @brief Initializes the ConnectIQ SDK with startup parameters necessary for +/// its operation. +/// +/// @param urlScheme The URL scheme for this companion app. When Garmin Connect +/// Mobile is launched, it will return to the companion app by +/// launching a URL with this scheme. +/// @param delegate The delegate that the SDK will use for notifying the +/// companion app about events that require user input. If this +/// is nil, the SDK's default UI will be used. +- (void)initializeWithUrlScheme:(NSString *)urlScheme uiOverrideDelegate:(id)delegate; + +// -------------------------------------------------------------------------------- +#pragma mark - EXTERNAL LAUNCHING +// -------------------------------------------------------------------------------- + +/// @brief Launches the Apple App Store page for the Garmin Connect Mobile app. +/// The companion app should only call this in response to a +/// needsToInstallConnectMobile event that gets triggered on the +/// IQUIOverrideDelegate. +- (void)showAppStoreForConnectMobile; + +/// @brief Launches Garmin Connect Mobile for the purpose of retrieving a list of +/// ConnectIQ-compatible devices. +/// +/// Once the user has chosen which ConnectIQ devices to share with the +/// companion app, GCM will return those devices to the companion app by +/// opening a URL with the scheme registered in +/// initializeWithUrlScheme:uiOverrideDelegate:. +/// +/// The companion app should handle this URL by passing it in to the +/// parseDeviceSelectionResponseFromURL: method to get the list of devices +/// that the user permitted the companion app to communicate with. +- (void)showConnectIQDeviceSelection; + +/// @brief Parses a URL opened from Garmin Connect Mobile into a list of devices. +/// +/// @param url The URL to parse. +/// +/// @return An array of IQDevice objects representing the ConnectIQ-compatible +/// devices that the user allowed GCM to share with the companion app. +/// +/// @seealso showConnectIQDeviceSelection +- (NSArray *)parseDeviceSelectionResponseFromURL:(NSURL *)url; + +/// @brief Launches Garmin Connect Mobile and shows the ConnectIQ app store page +/// for the given app. +/// +/// The companion app should call this if the user would like to manage +/// the app on the device, such as to install, upgrade, uninstall, or +/// modify settings. +/// +/// @param app The app to show the ConnectIQ app store page for. +- (void)showConnectIQStoreForApp:(IQApp *)app; + +// -------------------------------------------------------------------------------- +#pragma mark - DEVICE MANAGEMENT +// -------------------------------------------------------------------------------- + +/// @brief Registers an object as a listener for ConnectIQ device status events. +/// +/// A device may have multiple device event listeners if this method is +/// called more than once. +/// +/// @param device A device to listen for status events from. +/// @param delegate The listener which will receive status events for this device. +- (void)registerForDeviceEvents:(IQDevice *)device delegate:(id)delegate; + +/// @brief Unregisters a listener for a specific device. +/// +/// @param device The device to unregister the listener for. +/// @param delegate The listener to remove from the device. +- (void)unregisterForDeviceEvents:(IQDevice *)device delegate:(id)delegate; + +/// @brief Unregisters the specified listener from all devices for which it had +/// previously been registered. +/// +/// @param delegate The listener to unregister. +- (void)unregisterForAllDeviceEvents:(id)delegate; + +/// @brief Gets the current connection status of a device. +/// +/// The device must have been registered for event notifications by +/// calling registerForDeviceEvents:delegate: or this method will return +/// IQDeviceStatus_InvalidDevice. +/// +/// @param device The device to get the status for. +/// +/// @return The device's current connection status. +- (IQDeviceStatus)getDeviceStatus:(IQDevice *)device; + +// -------------------------------------------------------------------------------- +#pragma mark - APP MANAGEMENT +// -------------------------------------------------------------------------------- + +/// @brief Begins getting the status of an app on a device. This method returns +/// immediately. +/// +/// @param app The IQApp to get the status for. +/// @param completion The completion block that will be triggered when the device +/// status operation is complete. +- (void)getAppStatus:(IQApp *)app completion:(void(^)(IQAppStatus *appStatus))completion; + +/// @brief Registers an object as a listener for ConnectIQ messages from an app +/// on a device. +/// +/// An app may have multiple message listeners if this method is called +/// more than once. +/// +/// @param app The app to listen for messages from. +/// @param delegate The listener which will receive messages for this app. +- (void)registerForAppMessages:(IQApp *)app delegate:(id)delegate; + +/// @brief Unregisters a listener for a specific app. +/// +/// @param app The app to unregister a listener for. +/// @param delegate The listener to remove from the app. +- (void)unregisterForAppMessages:(IQApp *)app delegate:(id)delegate; + +/// @brief Unregisters all previously registered apps for a specific listener. +/// +/// @param delegate The listener to unregister. +- (void)unregisterForAllAppMessages:(id)delegate; + +/// @brief Begins sending a message to an app. This method returns immediately. +/// +/// @param message The message to send to the app. This message must be one of +/// the following types: NSString, NSNumber, NSNull, NSArray, +/// or NSDictionary. Arrays and dictionaries may be nested. +/// @param app The app to send the message to. +/// @param progress A progress block that will be triggered periodically +/// throughout the transfer. This is guaranteed to be triggered +/// at least once. +/// @param completion A completion block that will be triggered when the send +/// message operation is complete. +- (void)sendMessage:(id)message toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion; + +/// @brief Sends an open app request message request to the device. This method returns immediately. +/// +/// @param app The app to open. +/// @param completion A completion block that will be triggered when the send +/// message operation is complete. +- (void)openAppRequest:(IQApp *)app completion:(IQSendMessageCompletion)completion; + +// TODO *** Holding off on documenting this until this method actually works. +- (void)sendImage:(NSData *)bitmap toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQApp.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQApp.h new file mode 100644 index 000000000..a9dfe8c54 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQApp.h @@ -0,0 +1,34 @@ +// +// IQApp.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import +#import "IQDevice.h" +#import "IQAppStatus.h" + +/// @brief Represents an instance of a ConnectIQ app that is installed on a +/// Garmin device. +@interface IQApp : NSObject + +/// @brief The unique identifier for this app. +@property (nonatomic, readonly) NSUUID *uuid; + +/// @brief The unique identifier for this app in the store. +@property (nonatomic, readonly) NSUUID *storeUuid; + +/// @brief The device that this app is installed on. +@property (nonatomic, readonly) IQDevice *device; + +/// @brief Creates a new app instance. +/// +/// @param uuid The UUID of the app to create. +/// @param storeUuid The store UUID of the app to create. +/// @param device The device the app to create is installed on. +/// +/// @return A new IQApp instance with the appropriate values set. ++ (IQApp *)appWithUUID:(NSUUID *)uuid storeUuid:(NSUUID *)storeUuid device:(IQDevice *)device; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQAppStatus.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQAppStatus.h new file mode 100644 index 000000000..663641d15 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQAppStatus.h @@ -0,0 +1,20 @@ +// +// IQAppStatus.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import + +/// @brief Represents the current status of an app on a Garmin device. +@interface IQAppStatus : NSObject + +/// @brief YES if the app is installed on the device, NO if it isn't. +@property (nonatomic, readonly) BOOL isInstalled; + +/// @brief The version of the app that is currently installed on the device. If +/// the app is not installed, this value is unused. +@property (nonatomic, readonly) uint16_t version; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQConstants.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQConstants.h new file mode 100644 index 000000000..b9017759a --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQConstants.h @@ -0,0 +1,63 @@ +// +// IQConstants.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import + +/// @brief The current version of the ConnectIQ SDK. +extern int const IQSDKVersion; + +/// @brief The bundle identifier for the Garmin Connect Mobile app. +extern NSString * const IQGCMBundle; + +/// @brief The result of a SendMessage operation +typedef NS_ENUM(NSInteger, IQSendMessageResult){ + ///! @brief The message was sent successfully. + IQSendMessageResult_Success, + + /// @brief The message failed to send due to an unknown error. + IQSendMessageResult_Failure_Unknown, + + /// @brief The message failed to send. There was an error within the SDK or + /// on the device. + IQSendMessageResult_Failure_InternalError, + + /// @brief The message failed to send. The device is not available right now. + IQSendMessageResult_Failure_DeviceNotAvailable, + + /// @brief The message failed to send. The app is not installed on the + /// device. + IQSendMessageResult_Failure_AppNotFound, + + /// @brief The message failed to send. The device is busy and cannot receive + /// the message right now. + IQSendMessageResult_Failure_DeviceIsBusy, + + /// @brief The message failed to send. The message contained an unsupported + /// type. + IQSendMessageResult_Failure_UnsupportedType, + + /// @brief The message failed to send. The device does not have enough memory + /// to receive the message. + IQSendMessageResult_Failure_InsufficientMemory, + + /// @brief The message failed to send. The connection timed out while sending + /// the message. + IQSendMessageResult_Failure_Timeout, + + /// @brief The message failed to send and was retried, but could not complete + /// after a number of tries. + IQSendMessageResult_Failure_MaxRetries, + + /// @brief The message was received by the device but it chose not to display + /// a message prompt, ignoring the message. + IQSendMessageResult_Failure_PromptNotDisplayed, + + /// @brief The message was received by the device but the app to open + /// was already running on the device. + IQSendMessageResult_Failure_AppAlreadyRunning, +}; +NSString *NSStringFromSendMessageResult(IQSendMessageResult value); diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQDevice.h b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQDevice.h new file mode 100644 index 000000000..6842829f7 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Headers/IQDevice.h @@ -0,0 +1,61 @@ +// +// IQDevice.h +// ConnectIQ +// +// Copyright (c) 2014 Garmin. All rights reserved. +// + +#import +#import + +/// @brief The current status of an IQDevice. +typedef NS_ENUM(NSInteger, IQDeviceStatus){ + /// @brief No device with this UUID has been registered for status events + /// the SDK. + IQDeviceStatus_InvalidDevice, + + /// @brief Bluetooth is either powered off or resetting. + IQDeviceStatus_BluetoothNotReady, + + /// @brief This device could not be found by iOS. Perhaps the user removed + /// the device? + IQDeviceStatus_NotFound, + + /// @brief The device is recognized by iOS, but it is not currently + /// connected. + IQDeviceStatus_NotConnected, + + /// @brief The device is connected and ready to communicate. + IQDeviceStatus_Connected, +}; + +/// @brief Represents a ConnectIQ-compatible Garmin device. +@interface IQDevice : NSObject + +/// @brief The unique identifier for this device. +@property (nonatomic, readonly) NSUUID *uuid; + +/// @brief The model name of the device provided by Garmin Connect Mobile. +@property (nonatomic, readonly) NSString *modelName; + +/// @brief The friendly name of the device, set by the user and provided by +/// Garmin Connect Mobile. +@property (nonatomic, readonly) NSString *friendlyName; + +/// @brief Creates a new device instance. +/// +/// @param uuid The UUID of the device to create. +/// @param modelName The model name of the device to create. +/// @param friendlyName The friendly name of the device to create. +/// +/// @return A new IQDevice instance with the appropriate values set. ++ (IQDevice *)deviceWithId:(NSUUID *)uuid modelName:(NSString *)modelName friendlyName:(NSString *)friendlyName; + +/// @brief Creates a new device instance by copying another device's values. +/// +/// @param device The device to copy values from. +/// +/// @return A new IQDevice instance with all values copied. +- (instancetype)initWithDevice:(IQDevice *)device; + +@end diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Info.plist b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Info.plist new file mode 100644 index 000000000..97c2226f5 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Info.plist differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Modules/module.modulemap b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Modules/module.modulemap new file mode 100644 index 000000000..685a0721e --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module ConnectIQ { + umbrella header "ConnectIQ.h" + + export * + module * { export * } +} diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/_CodeSignature/CodeResources b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/_CodeSignature/CodeResources new file mode 100644 index 000000000..4abc9ad37 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/_CodeSignature/CodeResources @@ -0,0 +1,830 @@ + + + + + files + + Headers/ConnectIQ.h + + F1hICh90Ex4ADEjYLcSi0YPhrPA= + + Headers/IQApp.h + + R7+SmeArgBACIBWHRnEAugyFHKE= + + Headers/IQAppStatus.h + + WnybOSMMVqCKGns0rEz9C3EfQOg= + + Headers/IQConstants.h + + eI7keKSkaajUZACnuMhgtV1RuBA= + + Headers/IQDevice.h + + bl545C/cu0mw2KlRmzojKmHPom0= + + Info.plist + + J6UBHxpItQG74B8pJ3RVorbMl0o= + + Modules/module.modulemap + + SSRVAtIAdFmowQqE4HzOpWYLubg= + + ar.lproj/IQLocalizable.strings + + hash + + 1CDTE/Qaf1Z/HuhSt9CUnwitv4M= + + optional + + + cs.lproj/IQLocalizable.strings + + hash + + /jkyQ77G2Xd9wy6QptBphGNbtCY= + + optional + + + da.lproj/IQLocalizable.strings + + hash + + FYi0wjOu/Hw//Qe96yqxSb9yClc= + + optional + + + de.lproj/IQLocalizable.strings + + hash + + MitzVbGhXhTLjPvw9vuWcQQa50Q= + + optional + + + el.lproj/IQLocalizable.strings + + hash + + n82gLcjjjHszaroTFeJUvSrrc0o= + + optional + + + en.lproj/IQLocalizable.strings + + hash + + hcxxLyrTI+aElXlPc5dwr7jdqwc= + + optional + + + es.lproj/IQLocalizable.strings + + hash + + ff8DVQtNhO8pF7HFnXjh8foHXbo= + + optional + + + fi.lproj/IQLocalizable.strings + + hash + + R9cr8yqJmu91Xz31tGyprGR3t/s= + + optional + + + fr.lproj/IQLocalizable.strings + + hash + + PwFmqFeRTcjdHmkXYrPzNVYoe5o= + + optional + + + he.lproj/IQLocalizable.strings + + hash + + /jPUgFtYbbyELG5DZ3Sjoi/If9w= + + optional + + + hr.lproj/IQLocalizable.strings + + hash + + H2GtdTeORRPCnogvpWY69Dg9uME= + + optional + + + hu.lproj/IQLocalizable.strings + + hash + + QIimMhNyYmqp4ZW01hfj554WAMg= + + optional + + + id.lproj/IQLocalizable.strings + + hash + + 2/54a0gkcVuk1I3m4ulDAXOLL5o= + + optional + + + it.lproj/IQLocalizable.strings + + hash + + hNIKYIcP/87e6g7AUP+zKRtJ52M= + + optional + + + ja.lproj/IQLocalizable.strings + + hash + + 0iU2PbJ/3xgXMZ20ffsqaWpxKWc= + + optional + + + ko.lproj/IQLocalizable.strings + + hash + + ERH8oHR9H9jMHjP0EAgaTtVhnX4= + + optional + + + ms.lproj/IQLocalizable.strings + + hash + + DkbQA2+v/qSgQWma/fg3647Bkqs= + + optional + + + nb.lproj/IQLocalizable.strings + + hash + + T3zFOvuvrJt5Vnmfqt2Mf/du8as= + + optional + + + nl.lproj/IQLocalizable.strings + + hash + + t9PD5JEbfoSLaQ7f8M2cLghOReI= + + optional + + + pl.lproj/IQLocalizable.strings + + hash + + wfTnhBccAm6JfwH/JkZKNRKTUAU= + + optional + + + pt-PT.lproj/IQLocalizable.strings + + hash + + 7yXkcZEpJ4UiRHAzhK+vw/Q857Y= + + optional + + + pt.lproj/IQLocalizable.strings + + hash + + tZPncsQs8weCDJa03AKLpijXSUw= + + optional + + + ru.lproj/IQLocalizable.strings + + hash + + Ct+byJ3rWeigvg0q6rB/kQaR+yE= + + optional + + + sk.lproj/IQLocalizable.strings + + hash + + 1yTM1nAsAYpSH7NrYU6/nFlqk5E= + + optional + + + sv.lproj/IQLocalizable.strings + + hash + + i84z6vuHLrFpO0qZ2V0zYjixIws= + + optional + + + th.lproj/IQLocalizable.strings + + hash + + oW5npy+pDJM1wUOgTkw9FY1Ave4= + + optional + + + tr.lproj/IQLocalizable.strings + + hash + + 76rD7PLrQMiT5YTlI8IjEFgsiU4= + + optional + + + zh-Hans.lproj/IQLocalizable.strings + + hash + + DNlMxUKypOvKArzi7ioJUiFfFXg= + + optional + + + zh-Hant.lproj/IQLocalizable.strings + + hash + + U6I+uL07KIv2b77w0c0glaJlhMg= + + optional + + + + files2 + + Headers/ConnectIQ.h + + hash + + F1hICh90Ex4ADEjYLcSi0YPhrPA= + + hash2 + + ABtgvHbvmly4QpZO/KmmrwYkL0N+AqV3gXdPVrseysY= + + + Headers/IQApp.h + + hash + + R7+SmeArgBACIBWHRnEAugyFHKE= + + hash2 + + X4vXt0sO9gxQNzQalIaLqMpSGNRC9ue2USDcfjBYkec= + + + Headers/IQAppStatus.h + + hash + + WnybOSMMVqCKGns0rEz9C3EfQOg= + + hash2 + + tg9qNXtTmFUvNoJtq7O/aEXBNngcGENVRhvxLJ8C/xo= + + + Headers/IQConstants.h + + hash + + eI7keKSkaajUZACnuMhgtV1RuBA= + + hash2 + + bqDpm8yikc2FIqaSUHcLqPY6TPXLlXSUo+Dl9NUYwmA= + + + Headers/IQDevice.h + + hash + + bl545C/cu0mw2KlRmzojKmHPom0= + + hash2 + + 4N4+64IHeb9iBwyziNxo0SMuCM75ez9Em4UfmtgtTHA= + + + Modules/module.modulemap + + hash + + SSRVAtIAdFmowQqE4HzOpWYLubg= + + hash2 + + lQGjVO5Q0wfztjETCwDkwAkQ7nZInCgWdStnHL3o6Co= + + + ar.lproj/IQLocalizable.strings + + hash + + 1CDTE/Qaf1Z/HuhSt9CUnwitv4M= + + hash2 + + CWyQue2TCS0heGoGbN4ffetM2QZSk7lqgc2Wer2fgTg= + + optional + + + cs.lproj/IQLocalizable.strings + + hash + + /jkyQ77G2Xd9wy6QptBphGNbtCY= + + hash2 + + 1mSn+EYeYcTV1dArgHz7PkmZrV6mHWfnuG5aDa6Y87E= + + optional + + + da.lproj/IQLocalizable.strings + + hash + + FYi0wjOu/Hw//Qe96yqxSb9yClc= + + hash2 + + yLkvGzd+smkOjicvW/+Oe6wGGyirHS+/YfjuSzyVoMM= + + optional + + + de.lproj/IQLocalizable.strings + + hash + + MitzVbGhXhTLjPvw9vuWcQQa50Q= + + hash2 + + DFHv7MWBJmyAkOj993NmSFKbS2t8/vtSev603sBUtjI= + + optional + + + el.lproj/IQLocalizable.strings + + hash + + n82gLcjjjHszaroTFeJUvSrrc0o= + + hash2 + + i4FAK4mi+SgS6oZv8zM74kRZToakn49E8GD7FcJBLoQ= + + optional + + + en.lproj/IQLocalizable.strings + + hash + + hcxxLyrTI+aElXlPc5dwr7jdqwc= + + hash2 + + vmBi9DFJzFcG0OwaWKSDjgklNi407U8u2pz3EnEENN4= + + optional + + + es.lproj/IQLocalizable.strings + + hash + + ff8DVQtNhO8pF7HFnXjh8foHXbo= + + hash2 + + z6RjynaWjrRKHmv4sLirc4eXwKOtQdylzj5+TiHpaTc= + + optional + + + fi.lproj/IQLocalizable.strings + + hash + + R9cr8yqJmu91Xz31tGyprGR3t/s= + + hash2 + + 6BI0iPRVWaP63/XFdjLBz6z7DsvvuOoaEAS+mYzrx8E= + + optional + + + fr.lproj/IQLocalizable.strings + + hash + + PwFmqFeRTcjdHmkXYrPzNVYoe5o= + + hash2 + + geXjZzXre2CRiALecPFBGz4JSJA7MbkDnB4qrEMKNwk= + + optional + + + he.lproj/IQLocalizable.strings + + hash + + /jPUgFtYbbyELG5DZ3Sjoi/If9w= + + hash2 + + 47mcrSx16SFjWPIiN7guCAG0va8NiJ6I5s45tSVEHlY= + + optional + + + hr.lproj/IQLocalizable.strings + + hash + + H2GtdTeORRPCnogvpWY69Dg9uME= + + hash2 + + 4bQvygPax6VBpoFlyS5by1N6otnDMliHu+bWsDaWSQc= + + optional + + + hu.lproj/IQLocalizable.strings + + hash + + QIimMhNyYmqp4ZW01hfj554WAMg= + + hash2 + + 0m2fIyz26vh3RlUqqSXvoNTLovxIixrUyJoL/IDSoVk= + + optional + + + id.lproj/IQLocalizable.strings + + hash + + 2/54a0gkcVuk1I3m4ulDAXOLL5o= + + hash2 + + hQf9SrG7d8aVWsXIbCIxkKEJjbnW1FLvS+MbOI1VtHQ= + + optional + + + it.lproj/IQLocalizable.strings + + hash + + hNIKYIcP/87e6g7AUP+zKRtJ52M= + + hash2 + + XAbEWX6cicDxGzxGgSx3DhF4rjUHX4LV+dO0X3rUEqc= + + optional + + + ja.lproj/IQLocalizable.strings + + hash + + 0iU2PbJ/3xgXMZ20ffsqaWpxKWc= + + hash2 + + YOqOvZq0WEN4DCoSwc0lcTSRc4C812DqzjIsaid1SHg= + + optional + + + ko.lproj/IQLocalizable.strings + + hash + + ERH8oHR9H9jMHjP0EAgaTtVhnX4= + + hash2 + + WJyaRCWn1KqmcDeajRnC41MdNrlpbI+1JbPkXhbKrKY= + + optional + + + ms.lproj/IQLocalizable.strings + + hash + + DkbQA2+v/qSgQWma/fg3647Bkqs= + + hash2 + + gztYxa4Hn58HkKmcUIZI1jCz44IETZeMsqrpZSKxJvc= + + optional + + + nb.lproj/IQLocalizable.strings + + hash + + T3zFOvuvrJt5Vnmfqt2Mf/du8as= + + hash2 + + Oy6UOwSN+/xPIrthAEvzV8PEn27kfsHpMMLU5w1rww0= + + optional + + + nl.lproj/IQLocalizable.strings + + hash + + t9PD5JEbfoSLaQ7f8M2cLghOReI= + + hash2 + + XbijhSaZgmsW59Vo9ZEbhDuUQH18fHizWKzsLosiM0o= + + optional + + + pl.lproj/IQLocalizable.strings + + hash + + wfTnhBccAm6JfwH/JkZKNRKTUAU= + + hash2 + + MQYgqA+Hl03JJ261Q19K5Lt64kSTBP+pfpD+jOVE3AU= + + optional + + + pt-PT.lproj/IQLocalizable.strings + + hash + + 7yXkcZEpJ4UiRHAzhK+vw/Q857Y= + + hash2 + + seINq3QazVameLGOW+pIAtGWLa6NDl5XWRtqnObxywo= + + optional + + + pt.lproj/IQLocalizable.strings + + hash + + tZPncsQs8weCDJa03AKLpijXSUw= + + hash2 + + GnzdqEuQwORzVCih99bwr79UHIyzXm+zuN5b9m1NrKY= + + optional + + + ru.lproj/IQLocalizable.strings + + hash + + Ct+byJ3rWeigvg0q6rB/kQaR+yE= + + hash2 + + yCN9s/JXYqsMNZ1icaH4hUwyMQ1NtxOmV6sIAtRd9pc= + + optional + + + sk.lproj/IQLocalizable.strings + + hash + + 1yTM1nAsAYpSH7NrYU6/nFlqk5E= + + hash2 + + OFHDtkGLLSfTuSx8GOTycKDCKOKmX0Wh2QG1CHhRz3I= + + optional + + + sv.lproj/IQLocalizable.strings + + hash + + i84z6vuHLrFpO0qZ2V0zYjixIws= + + hash2 + + a3Gk+3USOT5uundOXrNCgnbcD0rDo2lkCO7b7+zg2Is= + + optional + + + th.lproj/IQLocalizable.strings + + hash + + oW5npy+pDJM1wUOgTkw9FY1Ave4= + + hash2 + + qxGqAqRMwm0/dMd0W7DUsvbWb9x65GT+3d1zOQEql1w= + + optional + + + tr.lproj/IQLocalizable.strings + + hash + + 76rD7PLrQMiT5YTlI8IjEFgsiU4= + + hash2 + + Y6TnKQmqO/TAx+0KYqRRG6UOz7I/gM1YmbUwgSfZSQU= + + optional + + + zh-Hans.lproj/IQLocalizable.strings + + hash + + DNlMxUKypOvKArzi7ioJUiFfFXg= + + hash2 + + BI3m4MTMHuPI4sQKPGeQnxIlBJJrXwgVuR7Ho1Q5o6Y= + + optional + + + zh-Hant.lproj/IQLocalizable.strings + + hash + + U6I+uL07KIv2b77w0c0glaJlhMg= + + hash2 + + 14dQnjX3pEz2Um4J/fOdQDRe/LSuXxqkg1hEkO8E5ys= + + optional + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ar.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ar.lproj/IQLocalizable.strings new file mode 100644 index 000000000..772c7c199 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ar.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/cs.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/cs.lproj/IQLocalizable.strings new file mode 100644 index 000000000..294594b65 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/cs.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/da.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/da.lproj/IQLocalizable.strings new file mode 100644 index 000000000..9c7faad3e Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/da.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/de.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/de.lproj/IQLocalizable.strings new file mode 100644 index 000000000..cb4d87b1b Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/de.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/el.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/el.lproj/IQLocalizable.strings new file mode 100644 index 000000000..8f4e27056 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/el.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/en.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/en.lproj/IQLocalizable.strings new file mode 100644 index 000000000..8794262a9 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/en.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/es.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/es.lproj/IQLocalizable.strings new file mode 100644 index 000000000..247b71ac7 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/es.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fi.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fi.lproj/IQLocalizable.strings new file mode 100644 index 000000000..344d06c1b Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fi.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fr.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fr.lproj/IQLocalizable.strings new file mode 100644 index 000000000..814e34f18 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/fr.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/he.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/he.lproj/IQLocalizable.strings new file mode 100644 index 000000000..b29a9b084 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/he.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hr.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hr.lproj/IQLocalizable.strings new file mode 100644 index 000000000..e10bd29af Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hr.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hu.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hu.lproj/IQLocalizable.strings new file mode 100644 index 000000000..9c96de1e8 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/hu.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/id.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/id.lproj/IQLocalizable.strings new file mode 100644 index 000000000..eda1bc3d3 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/id.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/it.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/it.lproj/IQLocalizable.strings new file mode 100644 index 000000000..1bd95ac77 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/it.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ja.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ja.lproj/IQLocalizable.strings new file mode 100644 index 000000000..0f5b121c0 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ja.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ko.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ko.lproj/IQLocalizable.strings new file mode 100644 index 000000000..3f3749b0e Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ko.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ms.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ms.lproj/IQLocalizable.strings new file mode 100644 index 000000000..499c806b2 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ms.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nb.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nb.lproj/IQLocalizable.strings new file mode 100644 index 000000000..436c9f67d Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nb.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nl.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nl.lproj/IQLocalizable.strings new file mode 100644 index 000000000..97d1c509c Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/nl.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pl.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pl.lproj/IQLocalizable.strings new file mode 100644 index 000000000..0f8f3d72b Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pl.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings new file mode 100644 index 000000000..6f9022878 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt-PT.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt.lproj/IQLocalizable.strings new file mode 100644 index 000000000..ddc7d69a1 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/pt.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ru.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ru.lproj/IQLocalizable.strings new file mode 100644 index 000000000..ce83c0a22 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/ru.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sk.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sk.lproj/IQLocalizable.strings new file mode 100644 index 000000000..98b954bff Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sk.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sv.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sv.lproj/IQLocalizable.strings new file mode 100644 index 000000000..316d6f897 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/sv.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/th.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/th.lproj/IQLocalizable.strings new file mode 100644 index 000000000..f98199499 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/th.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/tr.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/tr.lproj/IQLocalizable.strings new file mode 100644 index 000000000..c71eb4a00 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/tr.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings new file mode 100644 index 000000000..d630d3f0c Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hans.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings new file mode 100644 index 000000000..db0daa2c7 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework/ios-i386_x86_64-simulator/ConnectIQ.framework/zh-Hant.lproj/IQLocalizable.strings differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/ConnectIQ_iOS_SDK.html b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/ConnectIQ_iOS_SDK.html new file mode 100644 index 000000000..6b8b6c730 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/ConnectIQ_iOS_SDK.html @@ -0,0 +1,315 @@ + + + + + Connect IQ iOS SDK Documenation + + + + + + + + +
Connect IQ Mobile SDK
+ +
A Guide to building companion mobile apps on iOS for Monkey C applications
+ +
Version 1.4
+ +
+ +

Table of Contents

+ +

Getting Started

+ +

Introduction

+ +

The Connect IQ Mobile SDK allows companion iOS applications to be created that interact with Monkey C applications running on Garmin wearable devices. This allows feature-rich user experiences to be built by retrieving remote data or offloading resource-intensive tasks from the wearable device to the iOS device. This document will guide you through adding the Mobile SDK to an iOS project, as well as introducing the SDK’s API and how to communicate with your Monkey C apps.

+ +

Configuring a project to use the Mobile SDK

+ +

Add the framework to the project

+ +

The Mobile SDK for iOS is distributed as an iOS framework package and can be found in the root directory of the Connect IQ SDK. To enable the framework, + add an entry to the Target > General > Linked Frameworks and Libraries panel. To do this, click the + at the bottom of the Linked Frameworks and Libraries list, click Add Other..., browse for ConnectIQ.framework, and click OK.

+ +

+ +

You will be presented with a dialog box to choose which targets to add the Framework to. Ensure that the appropriate targets are checked and then click Finish.

+ +


+ +

Embed the ConnectIQ framework as a binary

+ +

To allow a project to build with the Mobile SDK, embed the framework as a binary for each of the project’s targets by adding an entry to the Target > General > Embedded Binaries panel.

+ +

+ +

Add required linker flags

+ +

The Mobile SDK for iOS utilizes category methods internally. When importing a library that uses category methods, an additional flag must be specified to allow the library to be linked correctly. To do this, add the –ObjC flag to the Target > Build Settings > Other Linker Flags setting.

+ +

+ +

Register a URL scheme

+ +

Unlike the Mobile SDK for Android, apps created with the Mobile SDK for iOS are standalone apps and do not directly rely on Garmin Connect Mobile (GCM) to communicate with a wearable device. They do, however, require GCM to initially discover Connect IQ-compatible devices that are available for communication, or to install Monkey C applications on the wearable device. The companion app and GCM exchange information by launching each other via the iOS URL scheme system. To facilitate this, a URL scheme that GCM can send data to must be registered by the app. To do this, add an entry to the Target > Info > URL Types panel. A string that is not likely to collide with any other apps on the iOS device should be chosen. See the Apple documentation for more information on custom URL schemes.

+ +

+ +

If you are compiling against the iOS 9 SDK or above you will need to add an entry for gcm-ciq to LSApplicationQueriesSchemes in your app’s Info.plist. This is needed to ensure that the SDK can verify if GCM is installed. If this key is not present in your Info.plist it will need to be added.

+ +

If your project doesn’t already set CFBundleDisplayName in your app’s Info.plist, you will need to add a definition. You can set this to ${PRODUCT_NAME} if you are unsure what value to use.

+ +

Set Bluetooth usage description

+ +

Starting in iOS 10 it is required to set strings explaining the usage of BLE peripheral access. The NSBluetoothPeripheralUsageDescription key must be set in Info.plist to explain your app’s usage of BLE to be accepted by iTunes Connect. If this key is not present in your Info.plist it will need to be added.

+ +

Set background execution mode (optional)

+ +

The iOS system allows apps that communicate with Bluetooth devices to be woken up to execute in the background when a connected device has data to send. This can be useful for companion apps that process requests for their respective Monkey C applications on a wearable device. To enable this behavior, turn on the Uses Bluetooth LE accessories option in the Target > Capabilities > Background Modes panel.

+ +

+ +

Initializing the SDK

+ +

All interaction with the Mobile SDK is done through the ConnectIQ class. This class must be initialized during app startup with the project’s URL scheme and a UI override delegate. Typically this is done within the app delegate’s application:didFinishLaunchingWithOptions: method.

+ +
[[ConnectIQ sharedInstance] initializeWithUrlScheme:@"exapp-123456"
+                                 uiOverrideDelegate:self];
+
+ +

The URL scheme should be the same string chosen in Step 4 of Configuring a project to use the Mobile SDK. +When invoking a method of the ConnectIQ class that requires GCM to be installed and it is not present on the iOS system, by default an alert dialog will be shown to the user that allows them to go to the Apple App Store page for GCM to install it. By passing an instance of an object that conforms to the IQUIOverrideDelegate protocol here, custom behavior or a specialized UI may be specified in this case. To use the default alert dialog and behavior, pass nil.

+ +

Implementing a UI override delegate

+ +

If a UI override delegate was specified and an action was performed that requires GCM to be installed, the ConnectIQ class will call the needsToInstallConnectMobile method on that delegate. The app should inform the user that GCM is required for this action and give the user the option to open the Apple App Store page for GCM or to cancel the action that triggered it. If the user chooses to install GCM, the showAppStoreForConnectMobile method may be called.

+ +
- (void)needsToInstallConnectMobile {
+    // Show alert to user with choice to install GCM
+    if (alert.result == YES) {
+       [[ConnectIQ sharedInstance] showAppStoreForConnectMobile];
+    }
+}
+
+ +

Note that this example is synchronous, but if UI is shown to the user, the showAppStoreForConnectMobile method should be called as a result of user input instead of directly in the needsToInstallConnectMobile method.

+ +

Working With Devices

+ +

Finding Connect IQ-compatible devices

+ +

The Mobile SDK for iOS can communicate directly with Connect IQ-compatible devices over Bluetooth. However, it must first know which devices are available. To accomplish this, a companion app must call the showConnectIQDeviceSelection method.

+ +
[[ConnectIQ sharedInstance] showConnectIQDeviceSelection];
+
+ +

This method launches GCM to the foreground and allows the user to choose which paired Connect IQ-compatible devices to share with the companion app. If GCM is not installed and a UI override delegate was set, its needsToInstallConnectMobile will be called.

+ +
+Note that by launching GCM, this method causes the companion app to go into the background, possibly resulting in the app being suspended. The companion app should expect to be suspended when calling this method. +
+ +

Once the user has selected which of the paired devices to share with the companion app, GCM will launch the companion app (via its registered URL scheme), passing the list of devices as serialized URL query items. The companion app should override its app delegate’s application:openURL:sourceApplication:annotation: method to listen for this. The companion app may then call the parseDeviceSelectionResponseFromURL: method to extract the query items into an NSArray of IQDevice objects that it can use with the Mobile SDK.

+ +
- (BOOL)application:(UIApplication *)application
+            openURL:(NSURL *)url
+  sourceApplication:(NSString *)sourceApplication
+         annotation:(id)annotation {
+    if ([url.scheme isEqualToString:ReturnURLScheme] &&
+        [sourceApplication isEqualToString:IQGCMBundle]) {
+
+        NSArray *devices = [[ConnectIQ sharedInstance]
+                             parseDeviceSelectionResponseFromURL:url];
+        if (devices != nil) {
+            [self.devices removeAllObjects];
+            for (IQDevice *device in devices) {
+                self.devices[device.uuid] = device;
+            }
+            return YES;
+        }
+    }
+    return NO;
+}
+
+ +

Note that in this example, the parsed devices are stored to a dictionary for later use within the app, but are not cached in any kind of persistent storage. +

+To avoid needing to launch GCM excessively to discover devices, companion apps should cache devices to persistent storage. +
+
+When a list of devices is returned by GCM, companion apps should clear all previously cached references to devices they may have known about. Always use only the latest list of devices that the user has authorized. +

+ +

Listening for device events

+ +

Once the companion app has one or more IQDevice instances from GCM, it may register with the ConnectIQ class to receive notifications when that device’s connection status changes by calling registerForDeviceEvents:delegate:.

+ +
[[ConnectIQ sharedInstance] registerForDeviceEvents:device
+                                           delegate:self];
+
+ +

The delegate passed in must be an instance of a class that conforms to the IQDeviceEventDelegate protocol. Once registered, the delegate’s deviceStatusChanged:status: method will be invoked when the device’s connection status changes. The getDeviceStatus: method may also be called to get the current connection status of the device. These methods both return a device’s status as an IQDeviceStatus value. +

+A companion app must register to receive device events before calling methods that operate on devices or apps, such as getDeviceStatus: or sendMessage:toApp:progress:completion:. +
+To stop listening for device events, a companion app may call either the unregisterForDeviceEvents:delegate: or unregisterForAllDeviceEvents: method.

+ +
// Stop listening to a single device
+[[ConnectIQ sharedInstance] unregisterForDeviceEvents:device
+                                             delegate:self];
+// ... or unregister all devices for this listener
+[[ConnectIQ sharedInstance] unregisterForAllDeviceEvents:self];
+
+ +

Working With Apps

+ +

Creating an app instance

+ +

Apps are represented in the Mobile SDK as instances of the IQApp class. An instance of the IQApp class represents a single app on a single device. This means that in order to work with an app that’s installed on two different devices, a companion app will need two instances of the IQApp class with the same app ID, one for each device. To create an app instance, use the IQApp class’s appWithUUID:device: method.

+ +
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@”<YourAppID>”];
+IQApp *app = [IQApp appWithUUID:uuid device:device];
+
+ +

Requesting an app’s status

+ +

Once an IQApp instance has been created that links an app ID to an IQDevice instance, a companion app may request the status of the app on that device by calling the getAppStatus:completion: method.

+ +
[[ConnectIQ sharedInstance] getAppStatus:app
+                              completion:^(IQAppStatus *appStatus) {
+    if (appStatus != nil && appStatus.isInstalled) {
+        NSLog(@”App is installed! Version: %d”, appStatus.version);
+    }
+}];
+
+ +

This method communicates with the device over Bluetooth, and therefore is asynchronous. The completion block will be invoked when the device responds, or the request times out. +If the request is successful, the completion block will be invoked with an instance of the IQAppStatus class. A companion app may inspect this status to discover if the app is installed on the device, and if so, what the version of that app is. A companion app could then potentially show a UI that recommends that the user upgrade the app on the device. +If the device is not currently connected or the request times out, the completion block will be invoked with a nil status.

+ +

Installing, upgrading, or managing an app

+ +

If a companion app determines that an app is out of date or not installed, it may allow the user to install or upgrade that app by launching the Connect IQ store within GCM. To do this, simply call the showConnectIQStoreForApp: method.

+ +
[[ConnectIQ sharedInstance] showConnectIQStoreForApp:app];
+
+ +

A companion app may also call this method even if the app is installed and up-to-date on the device, to allow the user to manage or uninstall the app from the device. +

+Like the showConnectIQDeviceSelection method, by launching GCM, this method causes the companion app to go into the background, possibly resulting in the app being suspended. The companion app should expect to be suspended when calling this method. +

+ +

Opening an app on the Garmin device

+ +

A companion app can request that a CIQ app be opened on the target device. When doing so a prompt will be displayed to the user on the Garmin device to see if they app should be opened. If the user chooses to open the app then it will be immediately opened. This can be accomplished by calling the openAppRequest: method.

+ +
[[ConnectIQ sharedInstance] openAppRequest:app
+                                completion:^(IQSendMessageResult result) {
+    switch(result) {
+        case IQSendMessageResult_Success: NSLog(@”Popup was displayed”); break;
+        case IQSendMessageResult_Failure_PromptNotDisplayed: NSLog(@”Popup was
+                displayed”); break;
+        case IQSendMessageResult_Failure_AppAlreadyRunning: NSLog(@”Popup was
+                displayed”); break;
+    }
+}];
+
+ +

Sending messages

+ +

Once a companion app has determined that an app is installed on a connected device, the companion app may send messages over Bluetooth to that app’s mailbox by calling the sendMessage:toApp:progress:completion: method. This method takes an object as a message, an IQApp as the destination, and two blocks – one that is invoked periodically as the data transfer progresses, and one that is invoked once the transfer is finished.

+ +
NSArray *message = @[@”hello pi”, @(3.14159)];
+[[ConnectIQ sharedInstance] sendMessage:message
+                                  toApp:app
+                               progress:^(uint32_t sent, uint32_t total) {
+    float percent = 100 * sent / (float)total;
+    NSLog(@"%02.2f%% - %u/%u", percent, sent, total);
+} completion:^(IQSendMessageResult result) {
+    NSLog(@"Send message finished with result %@",
+        NSStringFromSendMessageResult(result));
+}];
+
+ +
+The message object that is passed to this method is first converted by the SDK into a Monkey C-compatible type, and is then sent to the app’s mailbox on the device. Therefore, only Objective-C types that can be directly translated to comparable Monkey C types are valid. +
+ +

Valid message types include NSString, NSNumber, NSArray, NSDictionary, and NSNull. Take advantage of nesting other types inside an NSArray or NSDictionary to form complex messages. Values contained in NSNumber objects will be converted to the most appropriate Monkey C value type on the device. +

+Keep in mind that the wearable devices have limited memory and processing power compared to an iOS device. Messages should be as small as possible. However, sending frequent small messages can incur performance and battery life costs. Therefore, it is more desirable to send occasional large messages than it is to frequently send many tiny messages. Companion apps should aim to balance the costs of memory and performance by sending messages only when necessary and keeping message size to a minimum. +

+ +

Receiving messages

+ +

A companion app may register to receive messages that are sent from an app on a device by calling the registerForAppMessages:delegate: method. This method takes an IQApp to listen for messages from, and an instance of an object that conforms to the IQAppMessageDelegate protocol as the listener. After registering, when a message from that app is successfully received, the receivedMessage:fromApp: method will be called on the listener. +To stop listening for app messages, a companion app may call either the unregisterForAppMessages:delegate: or unregisterForAllAppMessages: method.

+ +
- (void)viewWillAppear:(BOOL)animated {
+    [[ConnectIQ sharedInstance] registerForAppMessages:self.app delegate:self];
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+    [[ConnectIQ sharedInstance] unregisterForAllAppMessages:self];
+}
+
+- (void)receivedMessage:(id)message fromApp:(IQApp *)app {
+    NSLog(@"Received message from app %@: '%@'", app, message);
+}
+
+ +
+A companion app may register to receive messages from multiple apps across many devices. However, multiple companion apps should never register to receive messages from the same app. The nature of Bluetooth communication on iOS prevents the Mobile SDK from determining which companion app to deliver the message to. Therefore, undefined behavior will result from multiple companion apps registering to receive messages from the same app. +
+ +

+

+ + + diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/connect_iq_logo.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/connect_iq_logo.png new file mode 100644 index 000000000..8ac537808 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/connect_iq_logo.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image1.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image1.png new file mode 100644 index 000000000..b0e57d557 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image1.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image10.jpeg b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image10.jpeg new file mode 100644 index 000000000..796416283 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image10.jpeg differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image2.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image2.png new file mode 100644 index 000000000..0071ec5de Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image2.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image3.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image3.png new file mode 100644 index 000000000..35505c723 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image3.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image4.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image4.png new file mode 100644 index 000000000..2f5907999 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image4.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image5.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image5.png new file mode 100644 index 000000000..b9c125e3d Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image5.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image6.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image6.png new file mode 100644 index 000000000..3abeb4603 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image6.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image7.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image7.png new file mode 100644 index 000000000..b0f5401af Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image7.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image8.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image8.png new file mode 100644 index 000000000..ab6c9f738 Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image8.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image9.png b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image9.png new file mode 100644 index 000000000..8491b3b0e Binary files /dev/null and b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/images/image9.png differ diff --git a/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/style.css b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/style.css new file mode 100644 index 000000000..53ff1c5a2 --- /dev/null +++ b/Dependencies/connectiq-mobile-sdk-ios-1.4/documentation/resources/style.css @@ -0,0 +1,140 @@ +/* unvisited link */ +a:link { + color: #01A6D2; + text-decoration: none; +} + +/* visited link */ +a:visited { + color: #01A6D2; +} + +/* mouse over link */ +a:hover { + color: #4DB849; + text-decoration: underline; +} + +/* selected link */ +a:active { + color: #333; +} + +body { + color: #333; + font-family: Georgia, "Times New Roman", Times, serif; + line-height: 1.5rem; + margin: 50px 100px 50px 100px; +} + +p>code { + color: #000; + background: #EEE; +} + +dd { + margin-bottom: 15px; + margin-left: 0px; +} + +dt { + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; +} + +figcaption { + color: #555; + font-family: Arial, Helvetica, sans-serif; + font-size: 0.75rem; + font-style: italic; +} + +figure { + margin: 0; + padding: 0; +} + +h1, h2, h3 { + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; +} + +h1 { + border-bottom: 2px solid #333; + padding-bottom: 8px; +} + +li { + margin-bottom: 5px; +} + +pre.prettyprint { + margin-left: 25px; +} + +table { + border-collapse: collapse; + width: 100%; +} + +td, th { + border: 1px solid #999; + font-family: Arial, Helvetica, sans-serif; + font-size: 0.9rem; + padding: 5px; +} + +td { + background-color: #D2E6F1; +} + +#connect_iq_logo { + clear: both; + float: right; + padding-bottom: 20px; +} + +#tagline { + clear: both; + color: #777; + float:right; + font-family: Arial, Helvetica, sans-serif; + font-size: 20px; +} + +#title { + clear: both; + float: right; + font-family: Arial, Helvetica, sans-serif; + font-size: 38px; + font-weight: bold; + margin-bottom: 10px; +} + +#version { + clear: both; + color: #777; + float:right; + font-family: Arial, Helvetica, sans-serif; + font-size: 20px; + margin-bottom: 80px; +} + +#warning{ + border: 1px solid; + border-color: #C0C0C0; + border-left-width: 7px; + border-left-color: #F3BB5A; + margin: 10px 0px; + padding:15px 10px 15px 50px; + color: #000000; + background-color: #F9DDA3; + background-image: url('warning.png'); + background-repeat: no-repeat; + background-position: 10px center; +} + +.clear { + clear: both; +} + diff --git a/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings b/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings index 5f8e4369c..a12af4d0c 100644 --- a/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/OmniKitUI/pt-PT.lproj/Localizable.strings @@ -60,7 +60,7 @@ "Bolus Delivery" = "Bolus Delivery"; /* The title of the cancel action in an action sheet */ -"Cancel" = "Cancel"; +"Cancel" = "Cancelar"; /* The title of the command to change pump time zone */ "Change Time Zone" = "Change Time Zone"; @@ -69,7 +69,7 @@ "Changing time…" = "Changing time…"; /* The title of the configuration section in settings */ -"Configuration" = "Configuration"; +"Configuration" = "Ajustes"; /* The title of the Insulin Type */ "Insulin Type" = "Insulin Type"; @@ -213,10 +213,10 @@ /* Title of button to save delivery limit settings Title of button to sync basal profile when no pod paired */ -"Save" = "Save"; +"Save" = "Salvar"; /* The detail text of the basal row when pod is running scheduled basal */ -"Schedule" = "Schedule"; +"Schedule" = "Agenda"; /* The title of the status section in settings */ "Status" = "Status"; diff --git a/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj b/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj index 121380f1c..10fd2c7cc 100644 --- a/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj +++ b/Dependencies/rileylink_ios/RileyLink.xcodeproj/project.pbxproj @@ -2767,9 +2767,9 @@ isa = PBXNativeTarget; buildConfigurationList = 431CE78A1F98564200255374 /* Build configuration list for PBXNativeTarget "RileyLinkBLEKit" */; buildPhases = ( + 431CE76C1F98564100255374 /* Headers */, 431CE76A1F98564100255374 /* Sources */, 431CE76B1F98564100255374 /* Frameworks */, - 431CE76C1F98564100255374 /* Headers */, 431CE76D1F98564100255374 /* Resources */, ); buildRules = ( @@ -2804,9 +2804,9 @@ isa = PBXNativeTarget; buildConfigurationList = 4352A72E20DEC9B700CAC200 /* Build configuration list for PBXNativeTarget "MinimedKitUI" */; buildPhases = ( + 4352A72220DEC9B700CAC200 /* Headers */, 4352A72020DEC9B700CAC200 /* Sources */, 4352A72120DEC9B700CAC200 /* Frameworks */, - 4352A72220DEC9B700CAC200 /* Headers */, 4352A72320DEC9B700CAC200 /* Resources */, ); buildRules = ( @@ -2824,9 +2824,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43722FC91CB9F7640038B7F2 /* Build configuration list for PBXNativeTarget "RileyLinkKit" */; buildPhases = ( + 43722FAB1CB9F7630038B7F2 /* Headers */, 43722FA91CB9F7630038B7F2 /* Sources */, 43722FAA1CB9F7630038B7F2 /* Frameworks */, - 43722FAB1CB9F7630038B7F2 /* Headers */, 43722FAC1CB9F7630038B7F2 /* Resources */, ); buildRules = ( @@ -2843,9 +2843,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43C2469E1D8918AE0031F8D1 /* Build configuration list for PBXNativeTarget "Crypto" */; buildPhases = ( + 43C246901D8918AE0031F8D1 /* Headers */, 43C2468E1D8918AE0031F8D1 /* Sources */, 43C2468F1D8918AE0031F8D1 /* Frameworks */, - 43C246901D8918AE0031F8D1 /* Headers */, 43C246911D8918AE0031F8D1 /* Resources */, ); buildRules = ( @@ -2861,9 +2861,9 @@ isa = PBXNativeTarget; buildConfigurationList = 43D5E7971FAF7BFB004ACDB7 /* Build configuration list for PBXNativeTarget "RileyLinkKitUI" */; buildPhases = ( + 43D5E78B1FAF7BFB004ACDB7 /* Headers */, 43D5E7891FAF7BFB004ACDB7 /* Sources */, 43D5E78A1FAF7BFB004ACDB7 /* Frameworks */, - 43D5E78B1FAF7BFB004ACDB7 /* Headers */, 43D5E78C1FAF7BFB004ACDB7 /* Resources */, ); buildRules = ( @@ -2880,9 +2880,9 @@ isa = PBXNativeTarget; buildConfigurationList = C10D9BD81C8269D600378342 /* Build configuration list for PBXNativeTarget "MinimedKit" */; buildPhases = ( + C10D9BBE1C8269D500378342 /* Headers */, C10D9BBC1C8269D500378342 /* Sources */, C10D9BBD1C8269D500378342 /* Frameworks */, - C10D9BBE1C8269D500378342 /* Headers */, C10D9BBF1C8269D500378342 /* Resources */, ); buildRules = ( @@ -2995,9 +2995,9 @@ isa = PBXNativeTarget; buildConfigurationList = C1B383221CD0665D00CE7782 /* Build configuration list for PBXNativeTarget "NightscoutUploadKit" */; buildPhases = ( + C1B383081CD0665D00CE7782 /* Headers */, C1B383061CD0665D00CE7782 /* Sources */, C1B383071CD0665D00CE7782 /* Frameworks */, - C1B383081CD0665D00CE7782 /* Headers */, C1B383091CD0665D00CE7782 /* Resources */, ); buildRules = ( @@ -3051,9 +3051,9 @@ isa = PBXNativeTarget; buildConfigurationList = C1FFAF93213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKit" */; buildPhases = ( + C1FFAF75213323CC00C50C1D /* Headers */, C1FFAF73213323CC00C50C1D /* Sources */, C1FFAF74213323CC00C50C1D /* Frameworks */, - C1FFAF75213323CC00C50C1D /* Headers */, C1FFAF76213323CC00C50C1D /* Resources */, ); buildRules = ( @@ -3089,9 +3089,9 @@ isa = PBXNativeTarget; buildConfigurationList = C1FFAFF0213323FA00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitUI" */; buildPhases = ( + C1FFAFD6213323F900C50C1D /* Headers */, C1FFAFD4213323F900C50C1D /* Sources */, C1FFAFD5213323F900C50C1D /* Frameworks */, - C1FFAFD6213323F900C50C1D /* Headers */, C1FFAFD7213323F900C50C1D /* Resources */, ); buildRules = ( diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings index c269b15ac..185b09e1b 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/it.lproj/Localizable.strings @@ -53,22 +53,22 @@ "Alert" = "Sveglia"; /* The title of the cell showing Low Battery Alert */ -"Low Battery Alert" = "Low Battery Alert"; +"Low Battery Alert" = "Avviso Batteria Bassa"; /* Header of list showing battery level alert options */ -"Battery level Alert" = "Battery level Alert"; +"Battery level Alert" = "Avviso livello batteria"; /* Battery level alert OFF in list of options */ -"OFF" = "OFF"; +"OFF" = "SPENTO"; /* The title of the command to update diagnostic LEDs */ -"Diagnostic LEDs" = "Diagnostic LEDs"; +"Diagnostic LEDs" = "LED Diagnostici"; /* The title of the command to fetch RileyLink statistics */ -"Get RileyLink Statistics" = "Get RileyLink Statistics"; +"Get RileyLink Statistics" = "Ottieni Statistiche Di RileyLink"; /* The title of the command to invert BLE connection LED logic */ -"Invert LED Logic" = "Invert LED Logic"; +"Invert LED Logic" = "Inverti Logica LED"; /* The header of the cells showing test commands */ "Test Commands" = "Test Commands"; diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings index 7cb0c55b3..2a18a80e0 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/pt-PT.lproj/Localizable.strings @@ -14,7 +14,7 @@ "Device" = "Device"; /* The title of the devices table section in RileyLink settings */ -"Devices" = "Devices"; +"Devices" = "Dispositivos"; /* The disconnected state */ "Disconnected" = "Disconnected"; @@ -29,7 +29,7 @@ "Frequency" = "Frequency"; /* The title of the cell showing device name */ -"Name" = "Name"; +"Name" = "Nome"; /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink allows for communication with the pump over Bluetooth Low Energy."; diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings index dcfff92b1..1158b6b87 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/sk.lproj/Localizable.strings @@ -98,7 +98,7 @@ "On" = "Zapnuté"; /* Text indicating LED Mode is off */ -"Off" = "Off"; +"Off" = "Vypnuté"; /* Text indicating LED Mode is auto */ "Auto" = "Auto"; diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index 166e418f4..514284769 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */; }; 0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5822B15939E719628E9FF7C /* SnoozeRootView.swift */; }; + 05A7F01E0F3586AE9B82F712 /* GarminConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A20F72CA2910F12549A964 /* GarminConfigProvider.swift */; }; 0CEA2EA070AB041AF3E3745B /* BolusRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */; }; 0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */; }; 0F7A65FBD2CD8D6477ED4539 /* NotificationsConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E625985B47742D498CB1681A /* NotificationsConfigProvider.swift */; }; @@ -16,6 +17,7 @@ 1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1927C8E82744606D00347C69 /* InfoPlist.strings */; }; 19795118275953E50044850D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; }; 198377D2266BFFF6004DE65E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; }; + 1A07D89433ED4C3C50465B9A /* GarminConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD3C4307A2DF7395F6AC770 /* GarminConfigDataFlow.swift */; }; 1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; }; 1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; }; 23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* BolusProvider.swift */; }; @@ -170,6 +172,9 @@ 38DF178D27733E6800B3528F /* snow.sks in Resources */ = {isa = PBXBuildFile; fileRef = 38DF178B27733E6800B3528F /* snow.sks */; }; 38DF178E27733E6800B3528F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 38DF178C27733E6800B3528F /* Assets.xcassets */; }; 38DF179027733EAD00B3528F /* SnowScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DF178F27733EAD00B3528F /* SnowScene.swift */; }; + 38DF17B32775DCA000B3528F /* GarminManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DF17B22775DC9F00B3528F /* GarminManager.swift */; }; + 38DF17BA2775E64400B3528F /* ConnectIQ.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38DF17B82775E39400B3528F /* ConnectIQ.xcframework */; }; + 38DF17BB2775E64400B3528F /* ConnectIQ.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 38DF17B82775E39400B3528F /* ConnectIQ.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E4451D274DB04600EC9A94 /* AppDelegate.swift */; }; 38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */; }; 38E44528274E401C00EC9A94 /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44527274E401C00EC9A94 /* Protected.swift */; }; @@ -246,15 +251,22 @@ 61962FCAF8A2D222553AC5A3 /* LibreConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A5B83E7967C38F7CBD883C /* LibreConfigDataFlow.swift */; }; 63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorStateModel.swift */; }; 642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */; }; + 6559084D2933A5BB00960C87 /* LibreViewConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6559084C2933A5BB00960C87 /* LibreViewConfigDataFlow.swift */; }; + 6559084F2933A5C800960C87 /* LibreViewConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6559084E2933A5C800960C87 /* LibreViewConfigProvider.swift */; }; + 655908512933A5D700960C87 /* LibreViewStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 655908502933A5D700960C87 /* LibreViewStateModel.swift */; }; + 655908542933A63300960C87 /* LibreViewConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 655908532933A63300960C87 /* LibreViewConfigRootView.swift */; }; + 65D16C5A29422ED100ABF87A /* LibreLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D16C5929422ED100ABF87A /* LibreLinkManager.swift */; }; 6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */; }; 69A31254F2451C20361D172F /* BolusStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */; }; 69B9A368029F7EB39F525422 /* CREditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA5E04A2761F6EEA6568E1 /* CREditorStateModel.swift */; }; 6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A708CDB546692C2230B385 /* SnoozeDataFlow.swift */; }; 6B9625766B697D1C98E455A2 /* PumpSettingsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72778B68C3004F71F6E79BDC /* PumpSettingsEditorStateModel.swift */; }; + 6D7E0B9D83F2B510D4440B8D /* GarminConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4034A76B4BEB9D792CC82E /* GarminConfigRootView.swift */; }; 6EADD581738D64431902AC0A /* LibreConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EBA7C03C26FCC67E16D798 /* LibreConfigProvider.swift */; }; 6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; }; 711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; }; 72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; }; + 77292D5C95BF610D1935A49D /* GarminConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8635114BF29577393D8A623 /* GarminConfigStateModel.swift */; }; 7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */; }; 7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A401509D21F7F35D4E109EDA /* DataTableDataFlow.swift */; }; 8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */; }; @@ -297,7 +309,8 @@ E013D872273AC6FE0014109C /* GlucoseSimulatorSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E013D871273AC6FE0014109C /* GlucoseSimulatorSource.swift */; }; E06B911A275B5EEA003C04B6 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06B9119275B5EEA003C04B6 /* Array+Extension.swift */; }; E0CC2C5C275B9F0F00A7BC71 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */; }; - E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */; }; + E0D4F80527513ECF00BDF1FE /* HealthKitSamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D4F80427513ECF00BDF1FE /* HealthKitSamples.swift */; }; + E0E9DB9727F051E700614A3F /* CarbsSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E9DB9627F051E700614A3F /* CarbsSource.swift */; }; E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */; }; E25073BC86C11C3D6A42F5AC /* CalibrationsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DFCE895C930F784EF11843 /* CalibrationsStateModel.swift */; }; E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */; }; @@ -307,11 +320,20 @@ E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */; }; F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */; }; F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */; }; + F904D3CD27A85B6800C5466F /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F904D3CC27A85B6800C5466F /* MigrationManager.swift */; }; F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692A9274B7AAE0037068D /* HealthKitManager.swift */; }; F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692CE274B999A0037068D /* HealthKitDataFlow.swift */; }; F90692D1274B99B60037068D /* HealthKitProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D0274B99B60037068D /* HealthKitProvider.swift */; }; F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */; }; F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D5274B9A450037068D /* HealthKitStateModel.swift */; }; + F97C8B2927B6A90200CB4A46 /* MigrationWorkItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C8B2827B6A90100CB4A46 /* MigrationWorkItem.swift */; }; + F97C8B2C27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C8B2B27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift */; }; + F97F722127A957AF007B6620 /* MigrationPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722027A957AF007B6620 /* MigrationPublisher.swift */; }; + F97F722427A973DF007B6620 /* MigrationDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722327A973DF007B6620 /* MigrationDataFlow.swift */; }; + F97F722627A973FF007B6620 /* MigrationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722527A973FF007B6620 /* MigrationProvider.swift */; }; + F97F722827A9741C007B6620 /* MigrationStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722727A9741C007B6620 /* MigrationStateModel.swift */; }; + F97F722B27A9743A007B6620 /* MigrationRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722A27A9743A007B6620 /* MigrationRootView.swift */; }; + F97F722D27A986F1007B6620 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97F722C27A986F1007B6620 /* AppInfo.swift */; }; FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */; }; /* End PBXBuildFile section */ @@ -346,6 +368,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 38DF17BB2775E64400B3528F /* ConnectIQ.xcframework in Embed Frameworks */, 3818AA6F274C26A500843DB3 /* RileyLinkKitUI.framework in Embed Frameworks */, 3818AA4B274C267100843DB3 /* CGMBLEKit.framework in Embed Frameworks */, 3818AA67274C26A400843DB3 /* OmniKit.framework in Embed Frameworks */, @@ -390,6 +413,7 @@ /* Begin PBXFileReference section */ 0274EE6439B1C3ED70730D41 /* PumpSettingsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorDataFlow.swift; sourceTree = ""; }; + 02A20F72CA2910F12549A964 /* GarminConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GarminConfigProvider.swift; sourceTree = ""; }; 0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorStateModel.swift; sourceTree = ""; }; 10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusRootView.swift; sourceTree = ""; }; 12204445D7632AF09264A979 /* PreferencesEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorDataFlow.swift; sourceTree = ""; }; @@ -575,6 +599,8 @@ 38DF178B27733E6800B3528F /* snow.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = snow.sks; sourceTree = ""; }; 38DF178C27733E6800B3528F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38DF178F27733EAD00B3528F /* SnowScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnowScene.swift; sourceTree = ""; }; + 38DF17B22775DC9F00B3528F /* GarminManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarminManager.swift; sourceTree = ""; }; + 38DF17B82775E39400B3528F /* ConnectIQ.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ConnectIQ.xcframework; path = "Dependencies/connectiq-mobile-sdk-ios-1.4/ConnectIQ.xcframework"; sourceTree = ""; }; 38E4451D274DB04600EC9A94 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManager.swift; sourceTree = ""; }; 38E44527274E401C00EC9A94 /* Protected.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protected.swift; sourceTree = ""; }; @@ -657,9 +683,15 @@ 60744C3E9BB3652895C908CC /* DataTableProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableProvider.swift; sourceTree = ""; }; 618E62C9757B2F95431B5DC0 /* AddCarbsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsProvider.swift; sourceTree = ""; }; 64AA5E04A2761F6EEA6568E1 /* CREditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorStateModel.swift; sourceTree = ""; }; + 6559084C2933A5BB00960C87 /* LibreViewConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreViewConfigDataFlow.swift; sourceTree = ""; }; + 6559084E2933A5C800960C87 /* LibreViewConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreViewConfigProvider.swift; sourceTree = ""; }; + 655908502933A5D700960C87 /* LibreViewStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreViewStateModel.swift; sourceTree = ""; }; + 655908532933A63300960C87 /* LibreViewConfigRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreViewConfigRootView.swift; sourceTree = ""; }; + 65D16C5929422ED100ABF87A /* LibreLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreLinkManager.swift; sourceTree = ""; }; 66A5B83E7967C38F7CBD883C /* LibreConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigDataFlow.swift; sourceTree = ""; }; 67F94DD2853CF42BA4E30616 /* BasalProfileEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorDataFlow.swift; sourceTree = ""; }; 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalProvider.swift; sourceTree = ""; }; + 6D4034A76B4BEB9D792CC82E /* GarminConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GarminConfigRootView.swift; sourceTree = ""; }; 6F8BA8533F56BC55748CA877 /* PreferencesEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorProvider.swift; sourceTree = ""; }; 72778B68C3004F71F6E79BDC /* PumpSettingsEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorStateModel.swift; sourceTree = ""; }; 79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = ""; }; @@ -684,6 +716,7 @@ AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigDataFlow.swift; sourceTree = ""; }; B5822B15939E719628E9FF7C /* SnoozeRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeRootView.swift; sourceTree = ""; }; B5EF98E22A39CD656A230704 /* AutotuneConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigProvider.swift; sourceTree = ""; }; + B8635114BF29577393D8A623 /* GarminConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GarminConfigStateModel.swift; sourceTree = ""; }; B8C7F882606FF83A21BE00D8 /* PumpSettingsEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorRootView.swift; sourceTree = ""; }; B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CGMDataFlow.swift; sourceTree = ""; }; BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorDataFlow.swift; sourceTree = ""; }; @@ -707,17 +740,28 @@ E013D871273AC6FE0014109C /* GlucoseSimulatorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseSimulatorSource.swift; sourceTree = ""; }; E06B9119275B5EEA003C04B6 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; - E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitSample.swift; sourceTree = ""; }; + E0D4F80427513ECF00BDF1FE /* HealthKitSamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitSamples.swift; sourceTree = ""; }; + E0E9DB9627F051E700614A3F /* CarbsSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsSource.swift; sourceTree = ""; }; E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeStateModel.swift; sourceTree = ""; }; E2EBA7C03C26FCC67E16D798 /* LibreConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigProvider.swift; sourceTree = ""; }; E625985B47742D498CB1681A /* NotificationsConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationsConfigProvider.swift; sourceTree = ""; }; E68CDC1E5C438D1BEAD4CF24 /* LibreConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigStateModel.swift; sourceTree = ""; }; E9AAB83FB6C3B41EFD1846A0 /* AddTempTargetRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetRootView.swift; sourceTree = ""; }; + EDD3C4307A2DF7395F6AC770 /* GarminConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GarminConfigDataFlow.swift; sourceTree = ""; }; + F904D3CC27A85B6800C5466F /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; F90692A9274B7AAE0037068D /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = ""; }; F90692CE274B999A0037068D /* HealthKitDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitDataFlow.swift; sourceTree = ""; }; F90692D0274B99B60037068D /* HealthKitProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitProvider.swift; sourceTree = ""; }; F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleHealthKitRootView.swift; sourceTree = ""; }; F90692D5274B9A450037068D /* HealthKitStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitStateModel.swift; sourceTree = ""; }; + F97C8B2827B6A90100CB4A46 /* MigrationWorkItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationWorkItem.swift; sourceTree = ""; }; + F97C8B2B27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceKeyAppLoading.swift; sourceTree = ""; }; + F97F722027A957AF007B6620 /* MigrationPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationPublisher.swift; sourceTree = ""; }; + F97F722327A973DF007B6620 /* MigrationDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationDataFlow.swift; sourceTree = ""; }; + F97F722527A973FF007B6620 /* MigrationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationProvider.swift; sourceTree = ""; }; + F97F722727A9741C007B6620 /* MigrationStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationStateModel.swift; sourceTree = ""; }; + F97F722A27A9743A007B6620 /* MigrationRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRootView.swift; sourceTree = ""; }; + F97F722C27A986F1007B6620 /* AppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfo.swift; sourceTree = ""; }; FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorRootView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -739,6 +783,7 @@ 3818AA6C274C26A500843DB3 /* RileyLinkKit.framework in Frameworks */, 3818AA68274C26A400843DB3 /* OmniKitUI.framework in Frameworks */, 3818AA5E274C26A300843DB3 /* MockKitUI.framework in Frameworks */, + 38DF17BA2775E64400B3528F /* ConnectIQ.xcframework in Frameworks */, 3818AA6E274C26A500843DB3 /* RileyLinkKitUI.framework in Frameworks */, 3818AA5A274C26A300843DB3 /* LoopKitUI.framework in Frameworks */, 38B17B6625DD90E0005CAE3D /* SwiftDate in Frameworks */, @@ -850,6 +895,7 @@ 3811DE0325C9D31700A708ED /* Modules */ = { isa = PBXGroup; children = ( + F97F722227A9739F007B6620 /* Migration */, F90692CD274B99850037068D /* HealthKit */, 6DC5D590658EF8B8DF94F9F5 /* AddCarbs */, A9A4C88374496B3C89058A89 /* AddTempTarget */, @@ -862,11 +908,14 @@ 0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */, E42231DBF0DBE2B4B92D1B15 /* CREditor */, 9E56E3626FAD933385101B76 /* DataTable */, + 6D92BE9AAFAFD64140B26462 /* GarminConfig */, + F90692CD274B99850037068D /* HealthKit */, 3811DE2725C9D49500A708ED /* Home */, D8F047E14D567F2B5DBEFD96 /* ISFEditor */, C11D545CED3ECEB525EDEE23 /* LibreConfig */, 3811DE1A25C9D48300A708ED /* Main */, 5031FE61F63C2A8A8B7674DD /* ManualTempBasal */, + 6559084B2933A58E00960C87 /* LibreViewConfig */, D533BF261CDC1C3F871E7BFD /* NightscoutConfig */, F66B236E00924A05D6A9F9DF /* NotificationsConfig */, 3E1C41D9301B7058AA7BF5EA /* PreferencesEditor */, @@ -980,6 +1029,7 @@ 3811DE9125C9D88200A708ED /* Services */ = { isa = PBXGroup; children = ( + F904D3CB27A85B5000C5466F /* Migration */, F90692A8274B7A980037068D /* HealthKit */, 38E8754D275556E100975559 /* WatchManager */, 38E87406274F9AA500975559 /* UserNotifiactions */, @@ -1011,6 +1061,7 @@ 3811DE9725C9D88300A708ED /* NightscoutManager.swift */, 38FE826925CC82DB001FF17A /* NetworkService.swift */, 38FE826C25CC8461001FF17A /* NightscoutAPI.swift */, + 65D16C5929422ED100ABF87A /* LibreLinkManager.swift */, ); path = Network; sourceTree = ""; @@ -1095,6 +1146,7 @@ 38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */, 38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */, 38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */, + E0E9DB9527F051C300614A3F /* Carbs */, 3856933F270B57A00002C50D /* CGM */, 38A504F625DDA0E200C5B9E8 /* Extensions */, 388E5A5825B6F0070019842D /* OpenAPS */, @@ -1114,6 +1166,7 @@ 3818AA48274C267000843DB3 /* Frameworks */ = { isa = PBXGroup; children = ( + 38DF17B82775E39400B3528F /* ConnectIQ.xcframework */, E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */, 38E87402274F78C000975559 /* libswiftCoreNFC.tbd */, 38E873FD274F761800975559 /* CoreNFC.framework */, @@ -1267,10 +1320,11 @@ 38E989DC25F5021400C0CED0 /* PumpStatus.swift */, 38BF021C25E7E3AF00579895 /* Reservoir.swift */, 3871F38625ED661C0013ECB5 /* Suggestion.swift */, + F97F722C27A986F1007B6620 /* AppInfo.swift */, 38A0364125ED069400FCBB52 /* TempBasal.swift */, 3871F39B25ED892B0013ECB5 /* TempTarget.swift */, 3811DE8E25C9D80400A708ED /* User.swift */, - E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */, + E0D4F80427513ECF00BDF1FE /* HealthKitSamples.swift */, ); path = Models; sourceTree = ""; @@ -1278,6 +1332,7 @@ 388E5A5A25B6F05F0019842D /* Helpers */ = { isa = PBXGroup; children = ( + F97C8B2A27B6C9FA00CB4A46 /* PreferenceKeys */, 38F37827261260DC009DB701 /* Color+Extensions.swift */, 389ECE042601144100D86C4F /* ConcurrentMap.swift */, 38192E0C261BAF980094D973 /* ConvenienceExtensions.swift */, @@ -1438,6 +1493,7 @@ isa = PBXGroup; children = ( 38E8754E275556FA00975559 /* WatchManager.swift */, + 38DF17B22775DC9F00B3528F /* GarminManager.swift */, ); path = WatchManager; sourceTree = ""; @@ -1588,6 +1644,25 @@ path = TargetsEditor; sourceTree = ""; }; + 6559084B2933A58E00960C87 /* LibreViewConfig */ = { + isa = PBXGroup; + children = ( + 655908522933A61D00960C87 /* View */, + 6559084C2933A5BB00960C87 /* LibreViewConfigDataFlow.swift */, + 6559084E2933A5C800960C87 /* LibreViewConfigProvider.swift */, + 655908502933A5D700960C87 /* LibreViewStateModel.swift */, + ); + path = LibreViewConfig; + sourceTree = ""; + }; + 655908522933A61D00960C87 /* View */ = { + isa = PBXGroup; + children = ( + 655908532933A63300960C87 /* LibreViewConfigRootView.swift */, + ); + path = View; + sourceTree = ""; + }; 672F63EEAE27400625E14BAD /* AutotuneConfig */ = { isa = PBXGroup; children = ( @@ -1599,6 +1674,17 @@ path = AutotuneConfig; sourceTree = ""; }; + 6D92BE9AAFAFD64140B26462 /* GarminConfig */ = { + isa = PBXGroup; + children = ( + EDD3C4307A2DF7395F6AC770 /* GarminConfigDataFlow.swift */, + 02A20F72CA2910F12549A964 /* GarminConfigProvider.swift */, + B8635114BF29577393D8A623 /* GarminConfigStateModel.swift */, + AF028C98DCC13FB4524BC8AF /* View */, + ); + path = GarminConfig; + sourceTree = ""; + }; 6DC5D590658EF8B8DF94F9F5 /* AddCarbs */ = { isa = PBXGroup; children = ( @@ -1678,6 +1764,14 @@ path = AddTempTarget; sourceTree = ""; }; + AF028C98DCC13FB4524BC8AF /* View */ = { + isa = PBXGroup; + children = ( + 6D4034A76B4BEB9D792CC82E /* GarminConfigRootView.swift */, + ); + path = View; + sourceTree = ""; + }; B9488883C59C31550E0B4CEC /* View */ = { isa = PBXGroup; children = ( @@ -1743,6 +1837,14 @@ path = Assemblies; sourceTree = ""; }; + E0E9DB9527F051C300614A3F /* Carbs */ = { + isa = PBXGroup; + children = ( + E0E9DB9627F051E700614A3F /* CarbsSource.swift */, + ); + path = Carbs; + sourceTree = ""; + }; E42231DBF0DBE2B4B92D1B15 /* CREditor */ = { isa = PBXGroup; children = ( @@ -1814,6 +1916,16 @@ path = CGM; sourceTree = ""; }; + F904D3CB27A85B5000C5466F /* Migration */ = { + isa = PBXGroup; + children = ( + F904D3CC27A85B6800C5466F /* MigrationManager.swift */, + F97F722027A957AF007B6620 /* MigrationPublisher.swift */, + F97C8B2827B6A90100CB4A46 /* MigrationWorkItem.swift */, + ); + path = Migration; + sourceTree = ""; + }; F90692A8274B7A980037068D /* HealthKit */ = { isa = PBXGroup; children = ( @@ -1841,6 +1953,33 @@ path = View; sourceTree = ""; }; + F97C8B2A27B6C9FA00CB4A46 /* PreferenceKeys */ = { + isa = PBXGroup; + children = ( + F97C8B2B27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift */, + ); + path = PreferenceKeys; + sourceTree = ""; + }; + F97F722227A9739F007B6620 /* Migration */ = { + isa = PBXGroup; + children = ( + F97F722927A97425007B6620 /* View */, + F97F722327A973DF007B6620 /* MigrationDataFlow.swift */, + F97F722527A973FF007B6620 /* MigrationProvider.swift */, + F97F722727A9741C007B6620 /* MigrationStateModel.swift */, + ); + path = Migration; + sourceTree = ""; + }; + F97F722927A97425007B6620 /* View */ = { + isa = PBXGroup; + children = ( + F97F722A27A9743A007B6620 /* MigrationRootView.swift */, + ); + path = View; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2080,6 +2219,7 @@ 38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */, 38C4D33725E9A1A300D30B77 /* DispatchQueue+Extensions.swift in Sources */, F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */, + F904D3CD27A85B6800C5466F /* MigrationManager.swift in Sources */, 3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */, 38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */, 38E4453C274E411700EC9A94 /* Disk+Codable.swift in Sources */, @@ -2140,6 +2280,7 @@ 388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */, 3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */, 3811DE3225C9D49500A708ED /* HomeDataFlow.swift in Sources */, + 6559084D2933A5BB00960C87 /* LibreViewConfigDataFlow.swift in Sources */, 38569347270B5DFB0002C50D /* CGMType.swift in Sources */, 3821ED4C25DD18BA00BC42AD /* Constants.swift in Sources */, 384E803425C385E60086DB71 /* JavaScriptWorker.swift in Sources */, @@ -2167,6 +2308,7 @@ 3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */, 38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */, 38BF021D25E7E3AF00579895 /* Reservoir.swift in Sources */, + F97C8B2927B6A90200CB4A46 /* MigrationWorkItem.swift in Sources */, 38BF021B25E7D06400579895 /* PumpSettingsView.swift in Sources */, 3862CC05273D152B00BF832C /* CalibrationService.swift in Sources */, 3811DEEA25CA063400A708ED /* SyncAccess.swift in Sources */, @@ -2197,11 +2339,14 @@ 9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */, 38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */, 642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */, + F97F722627A973FF007B6620 /* MigrationProvider.swift in Sources */, AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */, + F97C8B2C27B6CA0C00CB4A46 /* PreferenceKeyAppLoading.swift in Sources */, 53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */, 5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */, E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */, 448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */, + 6559084F2933A5C800960C87 /* LibreViewConfigProvider.swift in Sources */, 38E44536274E411700EC9A94 /* Disk.swift in Sources */, 2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */, 6B9625766B697D1C98E455A2 /* PumpSettingsEditorStateModel.swift in Sources */, @@ -2220,6 +2365,7 @@ 38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */, 385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */, F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */, + F97F722D27A986F1007B6620 /* AppInfo.swift in Sources */, 38887CCE25F5725200944304 /* IOBEntry.swift in Sources */, 38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */, CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */, @@ -2229,16 +2375,20 @@ DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */, 1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */, 38192E0D261BAF980094D973 /* ConvenienceExtensions.swift in Sources */, + 38DF17B32775DCA000B3528F /* GarminManager.swift in Sources */, 88AB39B23C9552BD6E0C9461 /* ISFEditorRootView.swift in Sources */, 38FEF413273B317A00574A46 /* HKUnit.swift in Sources */, A33352ED40476125EBAC6EE0 /* CREditorDataFlow.swift in Sources */, 17A9D0899046B45E87834820 /* CREditorProvider.swift in Sources */, 69B9A368029F7EB39F525422 /* CREditorStateModel.swift in Sources */, + F97F722827A9741C007B6620 /* MigrationStateModel.swift in Sources */, + 655908512933A5D700960C87 /* LibreViewStateModel.swift in Sources */, 38E44538274E411700EC9A94 /* Disk+[Data].swift in Sources */, 98641AF4F92123DA668AB931 /* CREditorRootView.swift in Sources */, 38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */, 38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */, F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */, + F97F722B27A9743A007B6620 /* MigrationRootView.swift in Sources */, 5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */, E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */, 9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */, @@ -2257,7 +2407,7 @@ D2165E9D78EFF692C1DED1C6 /* AddTempTargetDataFlow.swift in Sources */, 38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */, 5BFA1C2208114643B77F8CEB /* AddTempTargetProvider.swift in Sources */, - E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */, + E0D4F80527513ECF00BDF1FE /* HealthKitSamples.swift in Sources */, 919DBD08F13BAFB180DF6F47 /* AddTempTargetStateModel.swift in Sources */, 8BC2F5A29AD1ED08AC0EE013 /* AddTempTargetRootView.swift in Sources */, 38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */, @@ -2273,9 +2423,12 @@ 711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */, BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */, F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */, + F97F722427A973DF007B6620 /* MigrationDataFlow.swift in Sources */, C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */, 38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */, + F97F722127A957AF007B6620 /* MigrationPublisher.swift in Sources */, 7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */, + 655908542933A63300960C87 /* LibreViewConfigRootView.swift in Sources */, 38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */, 38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */, 3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */, @@ -2284,6 +2437,7 @@ A05235B9112E677ED03B6E8E /* AutotuneConfigRootView.swift in Sources */, 7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */, 1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */, + 65D16C5A29422ED100ABF87A /* LibreLinkManager.swift in Sources */, 0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */, D6D02515BBFBE64FEBE89856 /* DataTableRootView.swift in Sources */, 38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */, @@ -2296,6 +2450,7 @@ B7C465E9472624D8A2BE2A6A /* CalibrationsDataFlow.swift in Sources */, 320D030F724170A637F06D50 /* CalibrationsProvider.swift in Sources */, E25073BC86C11C3D6A42F5AC /* CalibrationsStateModel.swift in Sources */, + E0E9DB9727F051E700614A3F /* CarbsSource.swift in Sources */, BA90041DC8991147E5C8C3AA /* CalibrationsRootView.swift in Sources */, E3A08AAE59538BC8A8ABE477 /* NotificationsConfigDataFlow.swift in Sources */, 0F7A65FBD2CD8D6477ED4539 /* NotificationsConfigProvider.swift in Sources */, @@ -2305,6 +2460,10 @@ 6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */, 8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */, 0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */, + 1A07D89433ED4C3C50465B9A /* GarminConfigDataFlow.swift in Sources */, + 05A7F01E0F3586AE9B82F712 /* GarminConfigProvider.swift in Sources */, + 77292D5C95BF610D1935A49D /* GarminConfigStateModel.swift in Sources */, + 6D7E0B9D83F2B510D4440B8D /* GarminConfigRootView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2473,7 +2632,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.3; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2531,7 +2690,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.3; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -2556,7 +2715,7 @@ DEVELOPMENT_TEAM = "${DEVELOPER_TEAM}"; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = FreeAPS/Resources/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2569,6 +2728,7 @@ OTHER_LDFLAGS = ( "-weak_framework", CoreNFC, + "-ObjC", ); PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2592,7 +2752,7 @@ DEVELOPMENT_TEAM = "${DEVELOPER_TEAM}"; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = FreeAPS/Resources/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2605,6 +2765,7 @@ OTHER_LDFLAGS = ( "-weak_framework", CoreNFC, + "-ObjC", ); PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/FreeAPS/Resources/Info.plist b/FreeAPS/Resources/Info.plist index aa16d4682..5049c536f 100644 --- a/FreeAPS/Resources/Info.plist +++ b/FreeAPS/Resources/Info.plist @@ -35,6 +35,7 @@ LSApplicationQueriesSchemes + gcm-ciq xdripswift dexcomg6 dexcomcgm @@ -92,6 +93,8 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown + CFBundleDisplayName + $(APP_DISPLAY_NAME) UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/FreeAPS/Sources/APS/Carbs/CarbsSource.swift b/FreeAPS/Sources/APS/Carbs/CarbsSource.swift new file mode 100644 index 000000000..ecaf3fb54 --- /dev/null +++ b/FreeAPS/Sources/APS/Carbs/CarbsSource.swift @@ -0,0 +1,5 @@ +import Combine + +protocol CarbsSource: SourceInfoProvider { + func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never> +} diff --git a/FreeAPS/Sources/APS/FetchGlucoseManager.swift b/FreeAPS/Sources/APS/FetchGlucoseManager.swift index 9bf0cd9dc..f0a742306 100644 --- a/FreeAPS/Sources/APS/FetchGlucoseManager.swift +++ b/FreeAPS/Sources/APS/FetchGlucoseManager.swift @@ -9,11 +9,13 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable { private let processQueue = DispatchQueue(label: "BaseGlucoseManager.processQueue") @Injected() var glucoseStorage: GlucoseStorage! @Injected() var nightscoutManager: NightscoutManager! + @Injected() var libreLinkManager: LibreLinkManager! @Injected() var apsManager: APSManager! @Injected() var settingsManager: SettingsManager! @Injected() var libreTransmitter: LibreTransmitterSource! @Injected() var healthKitManager: HealthKitManager! @Injected() var deviceDataManager: DeviceDataManager! + @Injected() private var keychain: Keychain! private var lifetime = Lifetime() private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval) @@ -83,8 +85,10 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable { self.glucoseStorage.storeGlucose(filtered) self.apsManager.heartbeat(date: date) self.nightscoutManager.uploadGlucose() - let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) } + self.libreLinkManager.uploadIfNeeded() + + let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) } guard glucoseForHealth.isNotEmpty else { return } self.healthKitManager.saveIfNeeded(bloodGlucose: glucoseForHealth) } diff --git a/FreeAPS/Sources/APS/FetchTreatmentsManager.swift b/FreeAPS/Sources/APS/FetchTreatmentsManager.swift index 81be5a6b9..9b3fe1b0c 100644 --- a/FreeAPS/Sources/APS/FetchTreatmentsManager.swift +++ b/FreeAPS/Sources/APS/FetchTreatmentsManager.swift @@ -8,6 +8,7 @@ protocol FetchTreatmentsManager {} final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable { private let processQueue = DispatchQueue(label: "BaseFetchTreatmentsManager.processQueue") @Injected() var nightscoutManager: NightscoutManager! + @Injected() var healthKitManager: HealthKitManager! @Injected() var tempTargetsStorage: TempTargetsStorage! @Injected() var carbsStorage: CarbsStorage! @@ -22,15 +23,17 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable { private func subscribe() { timer.publisher .receive(on: processQueue) - .flatMap { _ -> AnyPublisher<([CarbsEntry], [TempTarget]), Never> in + .flatMap { _ -> AnyPublisher<([CarbsEntry], [CarbsEntry], [TempTarget]), Never> in debug(.nightscout, "FetchTreatmentsManager heartbeat") debug(.nightscout, "Start fetching carbs and temptargets") - return Publishers.CombineLatest( + return Publishers.CombineLatest3( self.nightscoutManager.fetchCarbs(), + self.healthKitManager.fetchCarbs(), self.nightscoutManager.fetchTempTargets() ).eraseToAnyPublisher() } - .sink { carbs, targets in + .sink { carbsFromNS, carbsFromAH, targets in + let carbs = carbsFromAH + carbsFromNS let filteredCarbs = carbs.filter { !($0.enteredBy?.contains(CarbsEntry.manual) ?? false) } if filteredCarbs.isNotEmpty { self.carbsStorage.storeCarbs(filteredCarbs) diff --git a/FreeAPS/Sources/APS/Storage/CarbsStorage.swift b/FreeAPS/Sources/APS/Storage/CarbsStorage.swift index 16c23f10d..78ebeac2d 100644 --- a/FreeAPS/Sources/APS/Storage/CarbsStorage.swift +++ b/FreeAPS/Sources/APS/Storage/CarbsStorage.swift @@ -65,7 +65,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable { func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment] { let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedPumphistory, as: [NigtscoutTreatment].self) ?? [] - let eventsManual = recent().filter { $0.enteredBy == CarbsEntry.manual } + let eventsManual = recent().filter { $0.enteredBy == CarbsEntry.manual || $0.enteredBy == CarbsEntry.applehealth } let treatments = eventsManual.map { NigtscoutTreatment( duration: nil, diff --git a/FreeAPS/Sources/Application/FreeAPSApp.swift b/FreeAPS/Sources/Application/FreeAPSApp.swift index e63d19dea..395b5dcae 100644 --- a/FreeAPS/Sources/Application/FreeAPSApp.swift +++ b/FreeAPS/Sources/Application/FreeAPSApp.swift @@ -1,9 +1,11 @@ +import Combine import SwiftUI import Swinject @main struct FreeAPSApp: App { @Environment(\.scenePhase) var scenePhase @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @State var loadingIsEnded: Bool = false // Dependencies Assembler // contain all dependencies Assemblies @@ -48,9 +50,32 @@ import Swinject var body: some Scene { WindowGroup { Main.RootView(resolver: resolver) + .onOpenURL(perform: handleURL) } .onChange(of: scenePhase) { newScenePhase in debug(.default, "APPLICATION PHASE: \(newScenePhase)") } } + + private func handleURL(_ url: URL) { + let components = URLComponents(url: url, resolvingAgainstBaseURL: false) + + switch components?.host { + case "device-select-resp": + resolver.resolve(NotificationCenter.self)!.post(name: .openFromGarminConnect, object: url) + default: break + } + } + + // Migration is temporary disabled + @ViewBuilder private var rootView: some View { + if !loadingIsEnded { + Screen.migration.view(resolver: resolver) + .onPreferenceChange(PreferenceKeyAppLoading.self) { + loadingIsEnded = $0 + } + } else { + Main.RootView(resolver: resolver) + } + } } diff --git a/FreeAPS/Sources/Assemblies/NetworkAssembly.swift b/FreeAPS/Sources/Assemblies/NetworkAssembly.swift index 619b81669..0e1d7a882 100644 --- a/FreeAPS/Sources/Assemblies/NetworkAssembly.swift +++ b/FreeAPS/Sources/Assemblies/NetworkAssembly.swift @@ -8,5 +8,6 @@ final class NetworkAssembly: Assembly { } container.register(NightscoutManager.self) { r in BaseNightscoutManager(resolver: r) } + container.register(LibreLinkManager.self) { r in BaseLibreLinkManager(resolver: r) } } } diff --git a/FreeAPS/Sources/Assemblies/ServiceAssembly.swift b/FreeAPS/Sources/Assemblies/ServiceAssembly.swift index 61c0f1459..6bbd880ed 100644 --- a/FreeAPS/Sources/Assemblies/ServiceAssembly.swift +++ b/FreeAPS/Sources/Assemblies/ServiceAssembly.swift @@ -18,6 +18,11 @@ final class ServiceAssembly: Assembly { container.register(HKHealthStore.self) { _ in HKHealthStore() } container.register(HealthKitManager.self) { r in BaseHealthKitManager(resolver: r) } container.register(UserNotificationsManager.self) { r in BaseUserNotificationsManager(resolver: r) } + container.register(GarminManager.self) { r in BaseGarminManager(resolver: r) } container.register(WatchManager.self) { r in BaseWatchManager(resolver: r) } + + // Migration service's + container.register(AppInfo.self) { _ in BaseAppInfo() } + container.register(MigrationManager.self) { r in BaseMigrationManager(resolver: r) } } } diff --git a/FreeAPS/Sources/Helpers/PreferenceKeys/PreferenceKeyAppLoading.swift b/FreeAPS/Sources/Helpers/PreferenceKeys/PreferenceKeyAppLoading.swift new file mode 100644 index 000000000..24acc772a --- /dev/null +++ b/FreeAPS/Sources/Helpers/PreferenceKeys/PreferenceKeyAppLoading.swift @@ -0,0 +1,9 @@ +import SwiftUI + +struct PreferenceKeyAppLoading: PreferenceKey { + typealias Value = Bool + static var defaultValue: Bool = false + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} diff --git a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings index a1a690af0..dd360f30d 100644 --- a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings @@ -513,7 +513,7 @@ Enact a temp Basal or a temp target */ "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Ukendt"; /* */ "Not paired yet" = "Not paired yet"; @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Tilslutningstilstand"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmer"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings index fc571c751..e3ac3adb8 100644 --- a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings @@ -1056,7 +1056,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "Minimaler Abstand (in Minuten), der zwischen zwei SMBs liegen muss"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Kleinstmögliche SMB-Menge in oref0. Für Medtronic ist der kleinstmögliche Betrag 0,1 U. Für Omnipod ist der kleinstmögliche Betrag 0,5 U."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Kleinstmögliche SMB-Menge in oref0. Für Medtronic ist der kleinstmögliche Betrag 0,1 U, für Omnipod 0,5 U. Der Standardwert ist 0,1."; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "Benutzerspezifische Zeit des maximalen Wirkungshochs des Insulins. Achtung: Oref wendet für die ultra-rapid (Lyumjev) & rapid-acting (Fiasp) Insulinwirkungs-Kurven Unter- (35 & 50 min) und Obergrenzen (100 & 120 min) an. Falls das benutzerdefinierte Wirkungshoch außerhalb dieser Grenzen liegt, kann es zu Komplikationen mit FreeAPS-X kommen, langen Loop-Berechnungszeiten und \"red loops\"."; diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index 48740e8ac..cf4971f11 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -70,6 +70,18 @@ /* Add carbs header and button in Watch app. You can skip the last " " space. It's just for differentiation */ "Add Carbs " = "Add Carbs "; +/* Fast Add carbs button */ +"Fast Add" = "Fast Add"; + +/* Fast Add carbs button description */ +"Carbs will add and FreeAPX X will update forecasts without bolus" = "Carbs will add and FreeAPX X will update forecasts without bolus"; + +/* Simple Add carbs button */ +"Simple Add" = "Simple Add"; + +/* Simple Add carbs button description*/ +"Carbs will add without bolus" = "Carbs will add without bolus"; + /* */ "Amount Carbs" = "Amount Carbs"; @@ -224,7 +236,7 @@ Enact a temp Basal or a temp target */ /* */ "Manual Temp Basal" = "Manual Temp Basal"; -/* Allow uploads tp NS */ +/* Allow uploads to different services */ "Allow uploads" = "Allow uploads"; /* API secret in NS */ @@ -944,10 +956,10 @@ Enact a temp Basal or a temp target */ "Connect to Apple Health" = "Connect to Apple Health"; /* Show when have not permissions for writing to Health */ -"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "For write data to Apple Health you must give permissions in Settings > Health > Data Access"; +"For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access" = "For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access"; /* */ -"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data"; +"After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data"; /* -------------------------------------------- */ /* @@ -1066,3 +1078,68 @@ Enact a temp Basal or a temp target */ /* "Noisy CGM Target Multiplier" */ "Defaults to 1.3. Increase target by this amount when looping off raw/noisy CGM data" = "Defaults to 1.3. Increase target by this amount when looping off raw/noisy CGM data"; + +/* Libre View --------------------*/ +/* */ +"LibreView config" = "LibreView config"; + +/* Secription on LibreView settings screen */ +"To use LibreView, you need to enter credentials and create a connection" = "To use LibreView, you need to enter credentials and create a connection"; + +/* Button on LibreView settings screen */ +"Update a connection" = "Update a connection"; + +/* Button on LibreView settings screen */ +"Create a connection" = "Create a connection"; + +/* Setting on LibreView settings screen */ +"Custom server" = "Custom server"; + +/* Section on LibreView settings screen */ +"Connection settings" = "Connection settings"; + +/* Button on LibreView settings screen */ +"Remove a connection" = "Remove a connection"; + +/* Alert message on LibreView settings screen */ +"Please, set correct LibreView server" = "Please, set correct LibreView server"; + +/* Setting on LibreView settings screen */ +"LibreView Server" = "LibreView Server"; + +/* Button on LibreView settings screen */ +"Force upload glocose" = "Force upload glocose"; + +/* Message after success uploading of glusoce to LibreView server */ +"Glucose was upload success" = "Glucose was upload success"; + +"Last upload date to LibreLink is more, that current date. Try later or check preferences" = "Last upload date to LibreLink is more, that current date. Try later or check preferences"; + +"Have not new glucose to upload" = "Have not new glucose to upload"; + +"Wrong connection's token" = "Wrong connection's token"; + +"Actions" = "Actions"; + +"Frequency of uploads" = "Frequency of uploads"; +"Each Loop" = "Each Loop"; +"Every 15 minutes" = "Every 15 minutes"; +"Every 30 minutes" = "Every 30 minutes"; +"Every hour" = "Every hour"; +"Every 4 hours" = "Every 4 hours"; +"Random upload, but less than 4 hours after the last one" = "Random upload, but less than 4 hours after the last one"; +"It is recommended to use random uploads, they are more natural" = "It is recommended to use random uploads, they are more natural"; +"Last upload on %@. Next upload no earlier than %@" = "Last upload on %@. Next upload no earlier than %@"; + +/* Credentials and autentification --------------------*/ +/* */ +"Credentials" = "Credentials"; + +/* */ +"Login" = "Login"; + +/* */ +"Password" = "Password"; + +/* */ +"Wrong password or login" = "Wrong password or login"; diff --git a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings index f9d124a23..4e4a733e7 100644 --- a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings @@ -68,7 +68,7 @@ "Add Carbs" = "Añadir Carbohidratos"; /* Add carbs header and button in Watch app. You can skip the last " " space. It's just for differentiation */ -"Add Carbs " = "Add Carbs "; +"Add Carbs " = "Agregar Carbohidratos "; /* */ "Amount Carbs" = "Cantidad de Carbohidratos"; @@ -417,10 +417,10 @@ Enact a temp Basal or a temp target */ "Temp Targets" = "Temp Targets"; /* Delete carbs from Treatments list*/ -"Delete carbs?" = "Delete carbs?"; +"Delete carbs?" = "¿Eliminar carbohidratos?"; /* Treatments list */ -"Treatments" = "Treatments"; +"Treatments" = "Tratamientos"; /* " min" in Treatments list */ " min" = " min"; @@ -429,19 +429,19 @@ Enact a temp Basal or a temp target */ /* Calendar and Libre transmitter settings --------------- */ /* */ -"Configure Libre Transmitter" = "Configure Libre Transmitter"; +"Configure Libre Transmitter" = "Configurar Transmisor Libre"; /* */ -"Calibrations" = "Calibrations"; +"Calibrations" = "Calibraciones"; /* */ -"Create events in calendar" = "Create events in calendar"; +"Create events in calendar" = "Crear eventos en el calendario"; /* */ -"Calendar" = "Calendar"; +"Calendar" = "Calendario"; /* */ -"Other" = "Other"; +"Other" = "Otro"; /* */ "Libre Transmitter" = "Libre Transmitter"; @@ -450,16 +450,16 @@ Enact a temp Basal or a temp target */ "Libre Transmitters" = "Libre Transmitters"; /* */ -"Bluetooth Transmitters" = "Bluetooth Transmitters"; +"Bluetooth Transmitters" = "Transmisores Bluetooth"; /* */ -"Modes" = "Modes"; +"Modes" = "Modos"; /* Libre 2 Direct */ -"Libre 2 Direct" = "Libre 2 Direct"; +"Libre 2 Direct" = "Libre 2 Directo"; /* */ -"Select the third party transmitter you want to connect to" = "Select the third party transmitter you want to connect to"; +"Select the third party transmitter you want to connect to" = "Seleccione el transmisor de terceros al que desea conectarse"; /* State was restored */ "State was restored" = "State was restored"; @@ -471,7 +471,7 @@ Enact a temp Basal or a temp target */ "mg/dL" = "mg/dL"; /* */ -"Add calibration" = "Add calibration"; +"Add calibration" = "Añadir calibración"; /* When adding capillary glucose meater reading */ "Meter glucose" = "Meter glucose"; @@ -486,16 +486,16 @@ Enact a temp Basal or a temp target */ "Intercept" = "Intercept"; /* */ -"Chart" = "Chart"; +"Chart" = "Gráfica"; /* */ -"Remove" = "Remove"; +"Remove" = "Eliminar"; /* */ -"Remove Last" = "Remove Last"; +"Remove Last" = "Eliminar último"; /* */ -"Remove All" = "Remove All"; +"Remove All" = "Eliminar todos"; /* */ "About the Process" = "About the Process"; @@ -513,10 +513,10 @@ Enact a temp Basal or a temp target */ "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Desconocido"; /* */ -"Not paired yet" = "Not paired yet"; +"Not paired yet" = "No emparejado aún"; /* */ "Pair Sensor & connect" = "Pair Sensor & connect"; @@ -615,13 +615,13 @@ Enact a temp Basal or a temp target */ "Current Sensor is Ending soon! Sensor Life left in %@" = "Current Sensor is Ending soon! Sensor Life left in %@"; /* */ -"Libre Bluetooth" = "Libre Bluetooth"; +"Libre Bluetooth" = "Bluetooth Libre"; /* */ "Snooze Alerts" = "Snooze Alerts"; /* */ -"Last measurement" = "Last measurement"; +"Last measurement" = "Última medición"; /* */ "Sensor Footer checksum" = "Sensor Footer checksum"; @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Estado de Conexión"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmas"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings index 14b454ffa..06e2da797 100644 --- a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings @@ -134,7 +134,7 @@ "for" = "for"; /* Temp target set for ... minutes */ -"min" = "min"; +"min" = "min."; /* */ "Autotune" = "Autotune"; @@ -513,7 +513,7 @@ Enact a temp Basal or a temp target */ "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Tuntematon"; /* */ "Not paired yet" = "Not paired yet"; @@ -657,10 +657,10 @@ Enact a temp Basal or a temp target */ "Hardware" = "Hardware"; /* */ -"Firmware" = "Firmware"; +"Firmware" = "Laiteohjelmisto"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Yhteyden tila"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Hälytykset"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings index 62a55bf68..a0e6565bc 100644 --- a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings @@ -131,10 +131,10 @@ "Top target" = "Target alto"; /* Temp target set for ... minutes */ -"for" = "for"; +"for" = "per"; /* Temp target set for ... minutes */ -"min" = "min"; +"min" = "minuti"; /* */ "Autotune" = "Autotune"; @@ -417,13 +417,13 @@ Enact a temp Basal or a temp target */ "Temp Targets" = "Obiettivi Temporanei"; /* Delete carbs from Treatments list*/ -"Delete carbs?" = "Delete carbs?"; +"Delete carbs?" = "Elimina carboidrati?"; /* Treatments list */ -"Treatments" = "Treatments"; +"Treatments" = "Trattamenti"; /* " min" in Treatments list */ -" min" = " min"; +" min" = " minuti"; /* Calendar and Libre transmitter settings --------------- @@ -624,7 +624,7 @@ Enact a temp Basal or a temp target */ "Last measurement" = "Ultima misurazione"; /* */ -"Sensor Footer checksum" = "Sensor Footer checksum"; +"Sensor Footer checksum" = "Somma di controllo del piè di pagina del sensore"; /* */ "Last Blood Sugar prediction" = "Ultima previsione di Zucchero nel Sangue"; @@ -822,82 +822,82 @@ Enact a temp Basal or a temp target */ "Found devices: %d" = "Dispositivi trovati: %d"; /* */ -"Backfill options" = "Backfill options"; +"Backfill options" = "Opzioni di riempimento"; /* */ -"Backfilling from trend is currently not well supported by Loop" = "Backfilling from trend is currently not well supported by Loop"; +"Backfilling from trend is currently not well supported by Loop" = "Il riempimento dalla tendenza non è attualmente ben supportato da Loop"; /* */ -"Backfill from history" = "Backfill from history"; +"Backfill from history" = "Riempi dalla cronologia"; /* */ -"Backfill from trend" = "Backfill from trend"; +"Backfill from trend" = "Riempimento dalla tendenza"; /* */ -"Debug options" = "Debug options"; +"Debug options" = "Opzioni di Debug"; /* */ -"Adds a lot of data to the Issue Report " = "Adds a lot of data to the Issue Report "; +"Adds a lot of data to the Issue Report " = "Aggiunge molti dati al Rapporto Problemi "; /* */ -"Persist sensordata" = "Persist sensordata"; +"Persist sensordata" = "Persistere i dati del sensore"; /* */ -"Battery" = "Battery"; +"Battery" = "Batteria"; /* */ -"Also add source info" = "Also add source info"; +"Also add source info" = "Aggiungi anche informazioni sorgente"; /* */ -"Carbs Required Threshold" = "Carbs Required Threshold"; +"Carbs Required Threshold" = "Soglia carboidrati necessari"; /* */ -"Carbs required: %d g" = "Carbs required: %d g"; +"Carbs required: %d g" = "Carboidrati necessari %d g"; /* */ -"To prevent LOW required %d g of carbs" = "To prevent LOW required %d g of carbs"; +"To prevent LOW required %d g of carbs" = "Per evitare glicemia BASSA servono %d g di carboidrati"; /* */ -"FreeAPS X not active" = "FreeAPS X not active"; +"FreeAPS X not active" = "FreeAPS X non attivo"; /* */ -"Last loop was more then %d min ago" = "Last loop was more then %d min ago"; +"Last loop was more then %d min ago" = "L'ultimo ciclo è stato più di %d min fa"; /* Glucose badge */ "Show glucose on the app badge" = "Mostra il glucosio sul badge dell'app"; /* */ -"Backfill glucose" = "Backfill glucose"; +"Backfill glucose" = "Riempi glicemia"; /* About this source */ -"About this source" = "About this source"; +"About this source" = "Informazioni su questa fonte"; /* */ -"Bolus failed" = "Bolus failed"; +"Bolus failed" = "Bolo fallito"; /* */ -"Bolus failed or inaccurate. Check pump history before repeating." = "Bolus failed or inaccurate. Check pump history before repeating."; +"Bolus failed or inaccurate. Check pump history before repeating." = "Bolo fallito o impreciso. Controlla la cronologia del microinfusore prima di ripetere."; /* */ -"Carbs" = "Carbs"; +"Carbs" = "Carboidrati"; /* */ -"Temp Basal" = "Temp Basal"; +"Temp Basal" = "Basale temporanea"; /* */ -"Temp Target" = "Temp Target"; +"Temp Target" = "Target Temporaneo"; /* */ -"Resume" = "Resume"; +"Resume" = "Riprendi"; /* */ -"Suspend" = "Suspend"; +"Suspend" = "Sospendi"; /* */ -"Animated Background" = "Animated Background"; +"Animated Background" = "Sfondo Animato"; /* Sensor day(s) */ -" day(s)" = " day(s)"; +" day(s)" = " giorno/i"; /* Headers for settings ----------------------- */ @@ -913,41 +913,41 @@ Enact a temp Basal or a temp target */ "Glucose Simulator" = "Glucose Simulator"; /* Restored state message */ -"Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@"; +"Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "Stato Bluetooth ripristinato (APS riavviato?). Trovato %d periferiche e collegato a %@ con identificatore %@"; /* Shared app group */ -"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Gruppo di app condiviso per il collegamento diretto con trasmettitori Libre 1 o sensori Europei Libre 2"; /* Native G6 app */ -"Native G6 app" = "Native G6 app"; +"Native G6 app" = "App nativa G6"; /* Native G5 app */ -"Native G5 app" = "Native G5 app"; +"Native G5 app" = "App nativa G5"; /* Minilink transmitter */ -"Minilink transmitter" = "Minilink transmitter"; +"Minilink transmitter" = "Trasmettitore Minilink"; /* Simple simulator */ -"Simple simulator" = "Simple simulator"; +"Simple simulator" = "Simulatore semplice"; /* Direct connection with Libre 1 transmitters or Libre 2 */ -"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Collegamento diretto con trasmettitori Libre 1 o sensori Europei Libre 2"; /* Online or internal server */ -"Online or internal server" = "Online or internal server"; +"Online or internal server" = "Server online o interno"; /* HealthKit intergration --------------------*/ /* */ "Apple Health" = "Apple Health"; /* */ -"Connect to Apple Health" = "Connect to Apple Health"; +"Connect to Apple Health" = "Connetti con Apple Health"; /* Show when have not permissions for writing to Health */ -"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "For write data to Apple Health you must give permissions in Settings > Health > Data Access"; +"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "Per scrivere dati su Apple Health devi dare il permesso in Impostazioni > Salute > Accesso dati"; /* */ -"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data"; +"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "Dopo aver creato record glicemici nell'app Salute, apri FreeAPS X per aiutarci a trasferire i dati modificati"; /* -------------------------------------------- @@ -1056,7 +1056,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "Durata minima in minuti tra due SMB sommimistrati"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Incremento SMB / SMB più piccolo in oref0. La quantità minima per microinfusore Medtronic è 0.1 U, mentre per Omnipod è 0.05 U. Il valore predefinito è 0.1."; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "Tempo del massimo effetto dell'insulina di riduzione del glucosio nel sangue, in minuti. Attenzione: Oref presume le curve ultra rapide (Lyumjev) e ad azione rapida (Fiasp) minime (35 e 50 min) e massime (100 e 120 min) degli insulinPeakTimes applicabili. Usare un insulinPeakTime personalizzato al di fuori di questi limiti risulterà in problemi con FreeAPS-X, calcoli ciclici più lunghi e possibili cicli rossi."; diff --git a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings index 6ad944f85..a523784f4 100644 --- a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings @@ -134,7 +134,7 @@ "for" = "for"; /* Temp target set for ... minutes */ -"min" = "min"; +"min" = "min."; /* */ "Autotune" = "Autotune"; @@ -515,7 +515,7 @@ Połączono z Nightscout!"; "Calibrationinfo" = "Calibrationinfo"; /* */ -"Unknown" = "Unknown"; +"Unknown" = "Nieznana"; /* */ "Not paired yet" = "Not paired yet"; @@ -659,10 +659,10 @@ Połączono z Nightscout!"; "Hardware" = "Hardware"; /* */ -"Firmware" = "Firmware"; +"Firmware" = "Oprogramowanie"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Status połączenia"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -695,7 +695,7 @@ Połączono z Nightscout!"; "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmy"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings index f6f533615..df4f624d1 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Estado da Conexão"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmes"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings index 7f78287be..55ef61ace 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings @@ -441,7 +441,7 @@ Enact a temp Basal or a temp target */ "Calendar" = "Calendar"; /* */ -"Other" = "Other"; +"Other" = "Outro"; /* */ "Libre Transmitter" = "Libre Transmitter"; diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index 6b55bfc2e..e847e812b 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -70,6 +70,18 @@ /* Add carbs header and button in Watch app. You can skip the last " " space. It's just for differentiation */ "Add Carbs " = "Ввод углеводов"; +/* Fast Add carbs button */ +"Fast Add" = "Добавить быстро"; + +/* Fast Add carbs button description */ +"Carbs will add and FreeAPX X will update forecasts without bolus" = "Углеводы будут добавлены без ввода болюса, FreeAPS X автоматически обновит прогнозы"; + +/* Simple Add carbs button */ +"Simple Add" = "Просто добавить"; + +/* Simple Add carbs button description*/ +"Carbs will add without bolus" = "Углеводы будут добавлены без ввода болюса"; + /* */ "Amount Carbs" = "Кол-во углеводов"; @@ -224,7 +236,7 @@ Enact a temp Basal or a temp target */ /* */ "Manual Temp Basal" = "Ручная ВБС"; -/* Allow uploads tp NS */ +/* Allow uploads data to different services */ "Allow uploads" = "Разрешить выгрузку"; /* API secret in NS */ @@ -897,7 +909,7 @@ Enact a temp Basal or a temp target */ "Animated Background" = "Анимированный фон"; /* Sensor day(s) */ -" day(s)" = " day(s)"; +" day(s)" = "дн."; /* Headers for settings ----------------------- */ @@ -916,7 +928,7 @@ Enact a temp Basal or a temp target */ "Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "Bluetooth состояние восстановлено (APS перезапущен?). Найдено %d периферийных устройств и подключено к %@ с идентификатором %@"; /* Shared app group */ -"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Общая группа приложений для прямой связи с трансмиттерами Libre 1 или Европейскими датчиками Libre 2"; /* Native G6 app */ "Native G6 app" = "Родное приложение G6"; @@ -931,7 +943,7 @@ Enact a temp Basal or a temp target */ "Simple simulator" = "Простой симулятор"; /* Direct connection with Libre 1 transmitters or Libre 2 */ -"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Прямое подключение к трансмиттерам Libre 1 или датчикам European Libre 2"; /* Online or internal server */ "Online or internal server" = "Онлайн или внутренний сервер"; @@ -944,10 +956,10 @@ Enact a temp Basal or a temp target */ "Connect to Apple Health" = "Подключить к Apple Health"; /* Show when have not permissions for writing to Health */ -"For write data to Apple Health you must give permissions in Settings > Health > Data Access" = "Чтобы записывать данные в Apple Health вам необходимо дать соответствующие разрешения, перейдя к меню Настройки > Здоровье > Доступ к данным"; +"For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access" = "Чтобы считывать и записывать данные из и в Apple Health вам необходимо дать соответствующие разрешения, перейдя в меню Настройки > Здоровье > Доступ к данным"; /* */ -"After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "После ручного создания записей о глюкозы в программе Здоровье пожалуйста откройте FreeAPS X, чтобы помочь нам гарантированно загрузить измененные данные"; +"After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" = "После ручного создания записей в программе Здоровье пожалуйста откройте FreeAPS X, чтобы помочь нам гарантированно загрузить измененные данные"; /* -------------------------------------------- @@ -1056,7 +1068,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "Минимальная продолжительность в минутах между подачей болюсов"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Самый маленький SMB / прирост SMB в oref0. Минимум для помп Медтроник равен 0.1 Ед., в то время как для Omnipod это 0.05 Ед. По умолчанию 0.1."; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "Пиковое время инсулина в минутах. Время необходимое инсулину для достижения максимального воздействия"; @@ -1066,3 +1078,68 @@ Enact a temp Basal or a temp target */ /* "Noisy CGM Target Multiplier" */ "Defaults to 1.3. Increase target by this amount when looping off raw/noisy CGM data" = "По умолчанию 1,3. Увеличивает цель на эту величину, когда цикл отключается из за шума сенсора"; + +/* Libre View --------------------*/ +/* */ +"LibreView config" = "Настройки LibreView"; + +/* Secription on LibreView settings screen */ +"To use LibreView, you need to enter credentials and create a connection" = "Для использования LibreView вам необходимо ввести данные авторизации и создать подключение"; + +/* Button on LibreView settings screen */ +"Update a connection" = "Обновить подключение"; + +/* Button on LibreView settings screen */ +"Create a connection" = "Создать подключение"; + +/* Setting on LibreView settings screen */ +"Custom server" = "Собственный сервер"; + +/* Section on LibreView settings screen */ +"Connection settings" = "Настройки подключения"; + +/* Button on LibreView settings screen */ +"Remove a connection" = "Удалить подключение"; + +/* Alert message on LibreView settings screen */ +"Please, set correct LibreView server" = "Пожалуйста, установите корректный адрес сервера"; + +/* Setting on LibreView settings screen */ +"LibreView Server" = "Сервер LibreView"; + +/* Button on LibreView settings screen */ +"Force upload glocose" = "Принудительная выгрузка глюкозы"; + +/* Message after success uploading of glusoce to LibreView server */ +"Glucose was upload success" = "Глюкоза была успешно выгружена"; + +"Last upload date to LibreLink is more, that current date. Try later or check preferences" = "Дата последней выгрузки больше текущей даты. Попробуйте позже или проверьте настройки"; + +"Have not new glucose to upload" = "Новая глюкоза для выгрузки отсутсвует"; + +"Wrong connection's token" = "Неверный токен безопасности"; + +"Actions" = "Действия"; + +"Frequency of uploads" = "Частота выгрузки"; +"Each Loop" = "Каждый цикл петли"; +"Every 15 minutes" = "Каждые 15 минут"; +"Every 30 minutes" = "Каждые 30 минут"; +"Every hour" = "Каждый час"; +"Every 4 hours" = "Каждые 4 часа"; +"Random upload, but less than 4 hours after the last one" = "Случайные выгрузки, но не менее 1 раза в 4 часа"; +"It is recommended to use random uploads, they are more natural" = "Рекомендовано использовать случайную частоту выгрузки, так как она более естественна"; +"Last upload on %@. Next upload no earlier than %@" = "Последняя выгрузка %@. Следующая не ранее, чем %@"; + +/* Credentials and autentification --------------------*/ +/* */ +"Credentials" = "Данные для входа"; + +/* */ +"Login" = "Логин"; + +/* */ +"Password" = "Пароль"; + +/* */ +"Wrong password or login" = "Неверный логин или пароль"; diff --git a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings index d965bbf27..e2a77af3c 100644 --- a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings @@ -660,7 +660,7 @@ Enact a temp Basal or a temp target */ "Firmware" = "Firmware"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Stav pripojenia"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Alarmy"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings index 821ffc4a4..4f4124aa0 100644 --- a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings @@ -657,10 +657,10 @@ Enact a temp Basal or a temp target */ "Hardware" = "Hardware"; /* */ -"Firmware" = "Firmware"; +"Firmware" = "Прошивка"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "Стан з'єднання"; /* */ "Transmitter Type" = "Transmitter Type"; @@ -693,7 +693,7 @@ Enact a temp Basal or a temp target */ "Advanced" = "Advanced"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "Тривога"; /* */ "Glucose Settings" = "Glucose Settings"; diff --git a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings index 87455d470..e39aed8d1 100644 --- a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings @@ -888,16 +888,16 @@ Enact a temp Basal or a temp target */ "Temp Target" = "临时目标"; /* */ -"Resume" = "Resume"; +"Resume" = "恢复"; /* */ "Suspend" = "暂停"; /* */ -"Animated Background" = "Animated Background"; +"Animated Background" = "动态背景"; /* Sensor day(s) */ -" day(s)" = " day(s)"; +" day(s)" = "天"; /* Headers for settings ----------------------- */ @@ -916,7 +916,7 @@ Enact a temp Basal or a temp target */ "Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@" = "蓝牙状态已恢复(APS 重启?) 找到 %d 个蓝牙,并连接到 %@ 标识符为 %@ 的设备"; /* Shared app group */ -"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Shared app group for direct connection with Libre 1 transmitters or European Libre 2 sensors" = "将连接瞬感1+发射器及直连瞬感 2 的第三方应用与 FreeAPS X 通过 AppGroup 方式共享数据"; /* Native G6 app */ "Native G6 app" = "官方G6 App"; @@ -931,7 +931,7 @@ Enact a temp Basal or a temp target */ "Simple simulator" = "简单模拟器"; /* Direct connection with Libre 1 transmitters or Libre 2 */ -"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Direct connection with Libre 1 transmitters or European Libre 2 sensors"; +"Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "直连Libre2或者Libre1发射器组合"; /* Online or internal server */ "Online or internal server" = "在线或内部服务器"; @@ -1058,7 +1058,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes between two enacted SMBs" = "两次自动大剂量的最短时间"; /* "Bolus Increment" */ -"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = "Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1."; +"Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1." = " oref0中SMB最小增量,美敦力为 0.1U, Omnipod 为 0.05U,默认值为 0.1"; /* "Insulin Peak Time" */ "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with FreeAPS-X, longer loop calculations and possible red loops." = "胰岛素的最大降血糖作用,以分钟为单位"; diff --git a/FreeAPS/Sources/Logger/Logger.swift b/FreeAPS/Sources/Logger/Logger.swift index 7a2b5acf5..ede39b821 100644 --- a/FreeAPS/Sources/Logger/Logger.swift +++ b/FreeAPS/Sources/Logger/Logger.swift @@ -111,6 +111,7 @@ final class Logger { static let deviceManager = Logger(category: .deviceManager, reporter: baseReporter) static let apsManager = Logger(category: .apsManager, reporter: baseReporter) static let nightscout = Logger(category: .nightscout, reporter: baseReporter) + static let librelink = Logger(category: .librelink, reporter: baseReporter) enum Category: String { case `default` @@ -120,6 +121,7 @@ final class Logger { case deviceManager case apsManager case nightscout + case librelink var name: String { rawValue.capitalizingFirstLetter() @@ -134,6 +136,7 @@ final class Logger { case .deviceManager: return .deviceManager case .apsManager: return .apsManager case .nightscout: return .nightscout + case .librelink: return .librelink } } @@ -144,6 +147,7 @@ final class Logger { case .apsManager, .businessLogic, .deviceManager, + .librelink, .nightscout, .openAPS, .service: diff --git a/FreeAPS/Sources/Models/AppInfo.swift b/FreeAPS/Sources/Models/AppInfo.swift new file mode 100644 index 000000000..f6f84fbdf --- /dev/null +++ b/FreeAPS/Sources/Models/AppInfo.swift @@ -0,0 +1,12 @@ +import Foundation + +protocol AppInfo { + // curent target/app version + var currentVersion: String { get } +} + +class BaseAppInfo: AppInfo { + var currentVersion: String { + Bundle.main.infoDictionary?["CFBundleVersion"] as! String + } +} diff --git a/FreeAPS/Sources/Models/CarbsEntry.swift b/FreeAPS/Sources/Models/CarbsEntry.swift index dbcbc237f..aa8333dbb 100644 --- a/FreeAPS/Sources/Models/CarbsEntry.swift +++ b/FreeAPS/Sources/Models/CarbsEntry.swift @@ -1,11 +1,13 @@ import Foundation struct CarbsEntry: JSON, Equatable, Hashable { + var id = UUID().uuidString let createdAt: Date let carbs: Decimal let enteredBy: String? static let manual = "freeaps-x" + static let applehealth = "applehealth" static func == (lhs: CarbsEntry, rhs: CarbsEntry) -> Bool { lhs.createdAt == rhs.createdAt @@ -17,6 +19,34 @@ struct CarbsEntry: JSON, Equatable, Hashable { } extension CarbsEntry { + private enum CodingKeys: String, CodingKey { + case id = "_id" + case createdAt = "created_at" + case carbs + case enteredBy + } +} + +// MARK: CarbsEntry till 0.2.6 + +// At this version was add id propery for working with Apple Health +struct CarbsEntryTill026: JSON, Equatable, Hashable { + let createdAt: Date + let carbs: Decimal + let enteredBy: String? + + static let manual = "freeaps-x" + + static func == (lhs: CarbsEntryTill026, rhs: CarbsEntryTill026) -> Bool { + lhs.createdAt == rhs.createdAt + } + + func hash(into hasher: inout Hasher) { + hasher.combine(createdAt) + } +} + +extension CarbsEntryTill026 { private enum CodingKeys: String, CodingKey { case createdAt = "created_at" case carbs diff --git a/FreeAPS/Sources/Models/FreeAPSSettings.swift b/FreeAPS/Sources/Models/FreeAPSSettings.swift index 817200d54..46baaa1d5 100644 --- a/FreeAPS/Sources/Models/FreeAPSSettings.swift +++ b/FreeAPS/Sources/Models/FreeAPSSettings.swift @@ -14,7 +14,6 @@ struct FreeAPSSettings: JSON, Equatable { var cgm: CGMType = .nightscout var uploadGlucose: Bool = false var useCalendar: Bool = false - var useAppleHealth: Bool = false var glucoseBadge: Bool = false var glucoseNotificationsAlways: Bool = false var useAlarmSound: Bool = false @@ -23,6 +22,17 @@ struct FreeAPSSettings: JSON, Equatable { var highGlucose: Decimal = 270 var carbsRequiredThreshold: Decimal = 10 var animatedBackground: Bool = false + + // Apple Health Settings + var useAppleHealth: Bool = false + + // LibreView Settings + var libreViewServer = 0 + var libreViewCustomServer = "" + var libreViewLastUploadTimestamp = 0.0 + var libreViewLastAllowUploadGlucose = false + var libreViewFrequenceUploads = 0 + var libreViewNextUploadDelta = 0.0 } extension FreeAPSSettings: Decodable { @@ -122,6 +132,30 @@ extension FreeAPSSettings: Decodable { settings.animatedBackground = animatedBackground } + if let libreViewServer = try? container.decode(Int.self, forKey: .libreViewServer) { + settings.libreViewServer = libreViewServer + } + + if let libreViewCustomServer = try? container.decode(String.self, forKey: .libreViewCustomServer) { + settings.libreViewCustomServer = libreViewCustomServer + } + + if let libreViewLastUploadTimestamp = try? container.decode(Double.self, forKey: .libreViewLastUploadTimestamp) { + settings.libreViewLastUploadTimestamp = libreViewLastUploadTimestamp + } + + if let libreViewLastAllowUploadGlucose = try? container.decode(Bool.self, forKey: .libreViewLastAllowUploadGlucose) { + settings.libreViewLastAllowUploadGlucose = libreViewLastAllowUploadGlucose + } + + if let libreViewFrequenceUploads = try? container.decode(Int.self, forKey: .libreViewFrequenceUploads) { + settings.libreViewFrequenceUploads = libreViewFrequenceUploads + } + + if let libreViewNextUploadDelta = try? container.decode(Double.self, forKey: .libreViewNextUploadDelta) { + settings.libreViewNextUploadDelta = libreViewNextUploadDelta + } + self = settings } } diff --git a/FreeAPS/Sources/Models/HealthKitSample.swift b/FreeAPS/Sources/Models/HealthKitSample.swift deleted file mode 100644 index 58b3f6845..000000000 --- a/FreeAPS/Sources/Models/HealthKitSample.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -struct HealthKitSample: JSON, Hashable, Equatable { - var healthKitId: String - var date: Date - var glucose: Int - - static func == (lhs: HealthKitSample, rhs: HealthKitSample) -> Bool { - lhs.healthKitId == rhs.healthKitId - } -} - -extension HealthKitSample { - private enum CodingKeys: String, CodingKey { - case healthKitId = "healthkit_id" - case date - case glucose - } -} diff --git a/FreeAPS/Sources/Models/HealthKitSamples.swift b/FreeAPS/Sources/Models/HealthKitSamples.swift new file mode 100644 index 000000000..569dea935 --- /dev/null +++ b/FreeAPS/Sources/Models/HealthKitSamples.swift @@ -0,0 +1,41 @@ +import Foundation + +// MARK: - Blood glucose + +struct HealthKitBGSample: JSON, Hashable, Equatable { + var healthKitId: String + var date: Date + var glucose: Int + + static func == (lhs: HealthKitBGSample, rhs: HealthKitBGSample) -> Bool { + lhs.healthKitId == rhs.healthKitId + } +} + +extension HealthKitBGSample { + private enum CodingKeys: String, CodingKey { + case healthKitId = "healthkit_id" + case date + case glucose + } +} + +// MARK: - Carbs + +struct HealthKitCarbsSample: JSON, Hashable, Equatable { + var healthKitId: String + var date: Date + var carbs: Decimal + + static func == (lhs: HealthKitCarbsSample, rhs: HealthKitCarbsSample) -> Bool { + lhs.healthKitId == rhs.healthKitId + } +} + +extension HealthKitCarbsSample { + private enum CodingKeys: String, CodingKey { + case healthKitId = "healthkit_id" + case date + case carbs + } +} diff --git a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift index 7bac8146c..303f70c2a 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift @@ -22,12 +22,34 @@ extension AddCarbs { CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual) ]) - if settingsManager.settings.skipBolusScreenAfterCarbs { - apsManager.determineBasalSync() + showModal(for: .bolus(waitForSuggestion: true)) + } + + func fastAdd() { + guard carbs > 0 else { + showModal(for: nil) + return + } + + carbsStorage.storeCarbs([ + CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual) + ]) + + apsManager.determineBasalSync() + showModal(for: nil) + } + + func addWithoutbolus() { + guard carbs > 0 else { showModal(for: nil) - } else { - showModal(for: .bolus(waitForSuggestion: true)) + return } + + carbsStorage.storeCarbs([ + CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual) + ]) + + showModal(for: nil) } } } diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index f694af8b1..52da812ab 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -38,6 +38,28 @@ extension AddCarbs { Button { state.add() } label: { Text("Add") } .disabled(state.carbs <= 0) + VStack(alignment: .leading, spacing: 5) { + Button { state.fastAdd() } + label: { Text("Fast Add") } + .disabled(state.carbs <= 0) + Text( + "Carbs will add and FreeAPX X will update forecasts without bolus" + ) + .font(.caption) + .foregroundColor(Color.secondary) + } + .padding(.top, 5) + VStack(alignment: .leading, spacing: 5) { + Button { state.addWithoutbolus() } + label: { Text("Simple Add") } + .disabled(state.carbs <= 0) + Text( + "Carbs will add without bolus" + ) + .font(.caption) + .foregroundColor(Color.secondary) + } + .padding(.top, 5) } } .onAppear(perform: configureView) diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift b/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift index 1a30a0d15..267dca125 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift @@ -52,7 +52,7 @@ enum DataTable { } class Treatment: Identifiable, Hashable, Equatable { - let id = UUID() + var id = UUID() let units: GlucoseUnits let type: DataType let date: Date @@ -67,6 +67,24 @@ enum DataTable { return formatter } + init( + id: UUID, + units: GlucoseUnits, + type: DataType, + date: Date, + amount: Decimal? = nil, + secondAmount: Decimal? = nil, + duration: Decimal? = nil + ) { + self.id = id + self.units = units + self.type = type + self.date = date + self.amount = amount + self.secondAmount = secondAmount + self.duration = duration + } + init( units: GlucoseUnits, type: DataType, @@ -172,6 +190,6 @@ protocol DataTableProvider: Provider { func tempTargets() -> [TempTarget] func carbs() -> [CarbsEntry] func glucose() -> [BloodGlucose] - func deleteCarbs(at date: Date) + func deleteCarbs(_ treatment: DataTable.Treatment) func deleteGlucose(id: String) } diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift b/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift index 134031c3b..34f05bead 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift @@ -21,8 +21,9 @@ extension DataTable { carbsStorage.recent() } - func deleteCarbs(at date: Date) { - nightscoutManager.deleteCarbs(at: date) + func deleteCarbs(_ treatment: Treatment) { + nightscoutManager.deleteCarbs(at: treatment.date) + healthkitManager.deleteCarbs(syncID: treatment.id.uuidString) } func glucose() -> [BloodGlucose] { @@ -31,7 +32,7 @@ extension DataTable { func deleteGlucose(id: String) { glucoseStorage.removeGlucose(ids: [id]) - healthkitManager.deleteGlucise(syncID: id) + healthkitManager.deleteGlucose(syncID: id) } } } diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift index 502413ed0..2f369cf57 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift @@ -24,7 +24,13 @@ extension DataTable { let units = self.settingsManager.settings.units let carbs = self.provider.carbs().map { - Treatment(units: units, type: .carbs, date: $0.createdAt, amount: $0.carbs) + Treatment( + id: UUID(uuidString: $0.id) ?? UUID(), + units: units, + type: .carbs, + date: $0.createdAt, + amount: $0.carbs + ) } let boluses = self.provider.pumpHistory() @@ -88,8 +94,8 @@ extension DataTable { } } - func deleteCarbs(at date: Date) { - provider.deleteCarbs(at: date) + func deleteCarbs(_ treatment: Treatment) { + provider.deleteCarbs(treatment) } func deleteGlucose(at index: Int) { diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index 1c5bb6cfc..04c185107 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -90,7 +90,9 @@ extension DataTable { message: Text(item.amountText), primaryButton: .destructive( Text("Delete"), - action: { state.deleteCarbs(at: item.date) } + action: { + state.deleteCarbs(item) + } ), secondaryButton: .cancel() ) diff --git a/FreeAPS/Sources/Modules/GarminConfig/GarminConfigDataFlow.swift b/FreeAPS/Sources/Modules/GarminConfig/GarminConfigDataFlow.swift new file mode 100644 index 000000000..fa4207c67 --- /dev/null +++ b/FreeAPS/Sources/Modules/GarminConfig/GarminConfigDataFlow.swift @@ -0,0 +1,5 @@ +enum GarminConfig { + enum Config {} +} + +protocol GarminConfigProvider {} diff --git a/FreeAPS/Sources/Modules/GarminConfig/GarminConfigProvider.swift b/FreeAPS/Sources/Modules/GarminConfig/GarminConfigProvider.swift new file mode 100644 index 000000000..8e9efa855 --- /dev/null +++ b/FreeAPS/Sources/Modules/GarminConfig/GarminConfigProvider.swift @@ -0,0 +1,3 @@ +extension GarminConfig { + final class Provider: BaseProvider, GarminConfigProvider {} +} diff --git a/FreeAPS/Sources/Modules/GarminConfig/GarminConfigStateModel.swift b/FreeAPS/Sources/Modules/GarminConfig/GarminConfigStateModel.swift new file mode 100644 index 000000000..2a89a2bd0 --- /dev/null +++ b/FreeAPS/Sources/Modules/GarminConfig/GarminConfigStateModel.swift @@ -0,0 +1,20 @@ +import ConnectIQ +import SwiftUI + +extension GarminConfig { + final class StateModel: BaseStateModel { + @Injected() private var garmin: GarminManager! + @Published var devices: [IQDevice] = [] + + override func subscribe() { + devices = garmin.devices + } + + func selectDevices() { + garmin.selectDevices() + .receive(on: DispatchQueue.main) + .weakAssign(to: \.devices, on: self) + .store(in: &lifetime) + } + } +} diff --git a/FreeAPS/Sources/Modules/GarminConfig/View/GarminConfigRootView.swift b/FreeAPS/Sources/Modules/GarminConfig/View/GarminConfigRootView.swift new file mode 100644 index 000000000..8afcbd5e1 --- /dev/null +++ b/FreeAPS/Sources/Modules/GarminConfig/View/GarminConfigRootView.swift @@ -0,0 +1,36 @@ +import SwiftUI +import Swinject + +extension GarminConfig { + struct RootView: BaseView { + let resolver: Resolver + @StateObject var state = StateModel() + + var body: some View { + Form { + Section { + Button("Select devices") { + state.selectDevices() + } + } + + if state.devices.isNotEmpty { + Section(header: Text("Connected devices")) { + ForEach(state.devices, id: \.uuid) { device in + Text(device.friendlyName) + } + } + } + + Section(header: Text("About")) { + Button("View sources on github.") { + UIApplication.shared.open(URL(string: "https://github.com/ivalkou/FreeAPSXGarmin")!) + } + } + } + .onAppear(perform: configureView) + .navigationTitle("Garmin Watch") + .navigationBarTitleDisplayMode(.automatic) + } + } +} diff --git a/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift b/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift index fa23644b2..85ddd279c 100644 --- a/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift +++ b/FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift @@ -25,7 +25,8 @@ extension AppleHealthKit { self.healthKitManager.requestPermission { ok, error in DispatchQueue.main.async { - self.needShowInformationTextForSetPermissions = !self.healthKitManager.checkAvailabilitySaveBG() + self.needShowInformationTextForSetPermissions = !self.healthKitManager.checkAvailabilitySaveBG() || !self + .healthKitManager.checkAvailabilitySaveCarbs() } guard ok, error == nil else { diff --git a/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift b/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift index b7594f3d4..aa67780f8 100644 --- a/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift +++ b/FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift @@ -13,7 +13,7 @@ extension AppleHealthKit { HStack { Image(systemName: "pencil.circle.fill") Text( - "After you create glucose records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" + "After you create records in the Health app, please open FreeAPS X to help us guaranteed transfer changed data" ) .font(.caption) } @@ -21,8 +21,10 @@ extension AppleHealthKit { if state.needShowInformationTextForSetPermissions { HStack { Image(systemName: "exclamationmark.circle.fill") - Text("For write data to Apple Health you must give permissions in Settings > Health > Data Access") - .font(.caption) + Text( + "For read/write data from/to Apple Health you must give permissions in Settings > Health > Data Access" + ) + .font(.caption) } .foregroundColor(Color.secondary) } diff --git a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift index 160cd6be0..36c3fc423 100644 --- a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift @@ -244,7 +244,7 @@ struct MainChartView: View { private func timeLabelsView(fullSize: CGSize) -> some View { ZStack { // X time labels - ForEach(0 ..< hours + hours) { hour in + ForEach(0 ..< hours + hours, id: \.self) { hour in Text(dateFormatter.string(from: firstHourDate().addingTimeInterval(hour.hours.timeInterval))) .font(.caption) .position( diff --git a/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewConfigDataFlow.swift b/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewConfigDataFlow.swift new file mode 100644 index 000000000..42759aed8 --- /dev/null +++ b/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewConfigDataFlow.swift @@ -0,0 +1,77 @@ +import Combine +import Foundation + +enum LibreViewConfig { + enum Config { + static let lvLoginKey = "LibreViewConfig.login" + static let lvPasswordKey = "LibreViewConfig.password" + static let lvTokenKey = "LibreViewConfig.token" + } + + enum Server: String, CaseIterable { + case custom + case ru = "api.libreview.ru" + case eu = "api-eu.libreview.io" + case us = "api-us.libreview.io" + case de = "api-de.libreview.io" + case fr = "api-fr.libreview.io" + case jp = "api-jp.libreview.io" + case ap = "api-ap.libreview.io" + case au = "api-au.libreview.io" + case ae = "api-ae.libreview.io" + + static func byViewTag(_ tag: Int) -> Self? { + switch tag { + case 0: return .custom + case 1: return .ru + case 2: return .eu + case 3: return .us + case 4: return .de + case 5: return .fr + case 6: return .jp + case 7: return .ap + case 8: return .au + case 9: return .ae + default: return nil + } + } + } + + enum UploadsFrequency: Int, CaseIterable { + case randomUpTo4Hours = 0 + case eachLoop + case minutes15 + case minutes30 + case hour1 + case hour4 + + var description: String { + switch self { + case .eachLoop: return NSLocalizedString("Each Loop", comment: "") + case .minutes15: return NSLocalizedString("Every 15 minutes", comment: "") + case .minutes30: return NSLocalizedString("Every 30 minutes", comment: "") + case .hour1: return NSLocalizedString("Every hour", comment: "") + case .hour4: return NSLocalizedString("Every 4 hours", comment: "") + case .randomUpTo4Hours: return NSLocalizedString( + "Random upload, but less than 4 hours after the last one", + comment: "" + ) + } + } + + var secondsToNextUpload: Double { + switch self { + case .eachLoop: return 0 + case .minutes15: return 900 + case .minutes30: return 1800 + case .hour1: return 3600 + case .hour4: return 14400 + case .randomUpTo4Hours: return Double(Int.random(in: 0 ... 14400)) + } + } + } +} + +protocol LibreViewConfigProvider: Provider { + func createConnection(url: URL, username: String, password: String) -> AnyPublisher +} diff --git a/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewConfigProvider.swift b/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewConfigProvider.swift new file mode 100644 index 000000000..f65cea94d --- /dev/null +++ b/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewConfigProvider.swift @@ -0,0 +1,12 @@ +import Combine +import Foundation + +extension LibreViewConfig { + final class Provider: BaseProvider, LibreViewConfigProvider { + @Injected() private var libreViewManager: LibreLinkManager! + + func createConnection(url: URL, username: String, password: String) -> AnyPublisher { + libreViewManager.createConnection(url: url, username: username, password: password) + } + } +} diff --git a/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewStateModel.swift b/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewStateModel.swift new file mode 100644 index 000000000..a38308428 --- /dev/null +++ b/FreeAPS/Sources/Modules/LibreViewConfig/LibreViewStateModel.swift @@ -0,0 +1,124 @@ +import Combine +import SwiftDate +import SwiftUI + +extension LibreViewConfig { + final class StateModel: BaseStateModel { + @Injected() private var keychain: Keychain! + @Injected() private var libreLinkManager: LibreLinkManager! + + @Published var login = "" + @Published var password = "" + @Published var token = "" + @Published var server = 0 + @Published var customServer = "" + @Published var allowUploadGlucose = false + + @Published var alertMessage: String? + @Published var lastUpload = 0.0 + @Published var uploadsFrequency = 0 + @Published var nextUploadDelta = 0.0 + + @Published var onLoading = false + @Published var onUploading = false + + let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .short + return formatter + }() + + override func subscribe() { + login = keychain.getValue(String.self, forKey: Config.lvLoginKey) ?? "" + password = keychain.getValue(String.self, forKey: Config.lvPasswordKey) ?? "" + token = keychain.getValue(String.self, forKey: Config.lvTokenKey) ?? "" + + $login.sink { [weak self] login in + self?.keychain.setValue(login, forKey: Config.lvLoginKey) + }.store(in: &lifetime) + + $password.sink { [weak self] password in + self?.keychain.setValue(password, forKey: Config.lvPasswordKey) + }.store(in: &lifetime) + + $token.sink { [weak self] token in + self?.keychain.setValue(token, forKey: Config.lvTokenKey) + }.store(in: &lifetime) + + subscribeSetting(\.libreViewServer, on: $server) { server = $0 } + subscribeSetting(\.libreViewCustomServer, on: $customServer) { customServer = $0 } + subscribeSetting(\.libreViewLastUploadTimestamp, on: $lastUpload) { lastUpload = $0 } + subscribeSetting(\.libreViewLastAllowUploadGlucose, on: $allowUploadGlucose) { allowUploadGlucose = $0 } + subscribeSetting(\.libreViewFrequenceUploads, on: $uploadsFrequency) { uploadsFrequency = $0 } + subscribeSetting(\.libreViewNextUploadDelta, on: $nextUploadDelta) { nextUploadDelta = $0 } + } + + func connect() { + guard let server = Server.byViewTag(server), + let url = server == .custom ? URL(string: customServer) : URL(string: "https://\(server.rawValue)") + else { + alertMessage = "Please, set correct LibreView server" + return + } + onLoading = true + provider.createConnection(url: url, username: login, password: password) + .receive(on: DispatchQueue.main) + .sink { [weak self] completion in + switch completion { + case .finished: break + case let .failure(error): + self?.alertMessage = error.localizedDescription + self?.token = "" + } + self?.onLoading = false + } receiveValue: { response in + self.token = response + } + .store(in: &lifetime) + } + + func forceUploadGlocose() { + guard let server = Server.byViewTag(server), + let url = server == .custom ? URL(string: customServer) : URL(string: "https://\(server.rawValue)") + else { + alertMessage = "Please, set correct LibreView server" + return + } + let currentTimestamp = Date().timeIntervalSince1970 + onUploading = true + libreLinkManager + .uploadGlucose( + url: url, + token: token, + from: lastUpload, + to: currentTimestamp + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] completion in + switch completion { + case .finished: break + case let .failure(error): + self?.alertMessage = error.localizedDescription + debug(.librelink, "Error during uploading data: \(error.localizedDescription)") + } + self?.onUploading = false + } receiveValue: { _ in + self.alertMessage = "Glucose was upload success" + self.onUploading = false + self.lastUpload = currentTimestamp + } + .store(in: &lifetime) + } + + func updateUploadTimestampDelta() { + guard let frequency = LibreViewConfig.UploadsFrequency(rawValue: uploadsFrequency) + else { + uploadsFrequency = 0 + nextUploadDelta = 0 + return + } + nextUploadDelta = frequency.secondsToNextUpload + } + } +} diff --git a/FreeAPS/Sources/Modules/LibreViewConfig/View/LibreViewConfigRootView.swift b/FreeAPS/Sources/Modules/LibreViewConfig/View/LibreViewConfigRootView.swift new file mode 100644 index 000000000..748bc9002 --- /dev/null +++ b/FreeAPS/Sources/Modules/LibreViewConfig/View/LibreViewConfigRootView.swift @@ -0,0 +1,133 @@ +import SwiftUI +import Swinject + +extension LibreViewConfig { + struct RootView: BaseView { + let resolver: Resolver + @StateObject var state = StateModel() + + var body: some View { + List { + credentialsSection + actionsSection + settingsSection + actionSection + } + .onAppear(perform: configureView) + .navigationBarTitle("LibreView") + .navigationBarTitleDisplayMode(.automatic) + .alert("Information", isPresented: Binding( + get: { state.alertMessage != nil }, + set: { _, _ in state.alertMessage = nil } + )) { + Button("OK", role: .cancel) {} + } message: { + Text(state.alertMessage ?? "") + } + } + + private var actionSection: some View { + Section("Actions") { + Button { + state.forceUploadGlocose() + } label: { + if state.onUploading { + ProgressView() + } else { + Text("Force upload glocose") + } + } + .disabled(state.onUploading) + } + } + + private var settingsSection: some View { + Section { + Picker("LibreView Server", selection: $state.server) { + ForEach(0 ..< Server.allCases.count, id: \.self) { index in + let server = Server.allCases[index].rawValue + Text(server).tag(server) + } + } + TextField("Custom server", text: $state.customServer) + .disableAutocorrection(true) + .autocapitalization(.none) + .textContentType(.URL) + .keyboardType(.URL) + Toggle("Allow uploads", isOn: $state.allowUploadGlucose) + Picker("Frequency of uploads", selection: $state.uploadsFrequency) { + ForEach(LibreViewConfig.UploadsFrequency.allCases, id: \.self) { frequencyItem in + Text(frequencyItem.description).tag(frequencyItem.rawValue) + } + } + .pickerStyle(.navigationLink) + .onChange(of: state.uploadsFrequency) { _ in state.updateUploadTimestampDelta() } + } header: { + Text("Connection settings") + } footer: { + Text("It is recommended to use random uploads, they are more natural") + } + } + + private var credentialsSection: some View { + Section { + TextField("Login", text: $state.login) + .disableAutocorrection(true) + .textContentType(.emailAddress) + .autocapitalization(.none) + .keyboardType(.emailAddress) + SecureField("Password", text: $state.password) + .disableAutocorrection(true) + .autocapitalization(.none) + .textContentType(.password) + .keyboardType(.asciiCapable) + } header: { + Text("Credentials") + } footer: { + if state.lastUpload > 0 { + Text( + "Last upload on \(state.dateFormatter.string(from: Date(timeIntervalSince1970: state.lastUpload))). Next upload no earlier than \(state.dateFormatter.string(from: Date(timeIntervalSince1970: state.lastUpload + state.nextUploadDelta)))" + ) + } + } + } + + private var actionsSection: some View { + Section { + if state.token != "" { + Button { + state.connect() + } label: { + if state.onLoading { + ProgressView() + } else { + Text("Update a connection") + } + } + .disabled(state.onLoading) + Button { + state.token = "" + } label: { + Text("Remove a connection") + .foregroundColor(.red) + } + } else { + Button { + state.connect() + } label: { + if state.onLoading { + ProgressView() + } else { + Text("Create a connection") + } + } + .disabled(state.onLoading) + } + } footer: { + if state.token == "" { + Text("To use LibreView, you need to enter credentials and create a connection") + } + } + } + } +} diff --git a/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift b/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift index 4b230ec9c..8f81cb0cd 100644 --- a/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift +++ b/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift @@ -23,7 +23,7 @@ extension ManualTempBasal { Text("U/hr").foregroundColor(.secondary) } Picker(selection: $state.durationIndex, label: Text("Duration")) { - ForEach(0 ..< state.durationValues.count) { index in + ForEach(0 ..< state.durationValues.count, id: \.self) { index in Text( String( format: "%.0f h %02.0f min", diff --git a/FreeAPS/Sources/Modules/Migration/MigrationDataFlow.swift b/FreeAPS/Sources/Modules/Migration/MigrationDataFlow.swift new file mode 100644 index 000000000..27b666f57 --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/MigrationDataFlow.swift @@ -0,0 +1,7 @@ +import SwiftUI + +enum Migration { + enum Config {} +} + +protocol MigrationProvider: Provider {} diff --git a/FreeAPS/Sources/Modules/Migration/MigrationProvider.swift b/FreeAPS/Sources/Modules/Migration/MigrationProvider.swift new file mode 100644 index 000000000..5528687f7 --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/MigrationProvider.swift @@ -0,0 +1,5 @@ +import Combine + +extension Migration { + final class Provider: BaseProvider, MainProvider {} +} diff --git a/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift new file mode 100644 index 000000000..f8d115fdc --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/MigrationStateModel.swift @@ -0,0 +1,27 @@ +import Combine +import SwiftUI +import Swinject + +extension Migration { + @MainActor final class StateModel: BaseStateModel { + @Injected() private var manager: MigrationManager! + + @Published var animated: Bool = false + @Published var loadingIsEnded: Bool = false + + func runMigration() { + debug(.businessLogic, "Migration did start on current version \(manager.appInfo.currentVersion)") + Publishers + .getMigrationPublisher(fromMigrationManager: manager) + .migrate(startAtVersion: "0.2.6", MigrationCarbs()) + .sink { _ in + debug(.businessLogic, "Migration did finish") + // fake pause + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.loadingIsEnded = true + } + } + .store(in: &lifetime) + } + } +} diff --git a/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift new file mode 100644 index 000000000..ef08afcc2 --- /dev/null +++ b/FreeAPS/Sources/Modules/Migration/View/MigrationRootView.swift @@ -0,0 +1,46 @@ +import Combine +import SwiftUI +import Swinject + +extension Migration { + struct RootView: BaseView { + let resolver: Resolver + @StateObject var state = StateModel() + + var body: some View { + VStack { + Circle() + .trim(from: 0.1, to: 0.9) + .stroke( + AngularGradient(gradient: Gradient(colors: [.red, .yellow]), center: .center), + style: StrokeStyle(lineWidth: 15, lineCap: .round) + ) + .frame(width: 80, height: 80) + .rotationEffect(.degrees(state.animated ? 360 : 0)) + .animation(.linear(duration: 0.7).repeatForever(autoreverses: false)) + VStack(spacing: 0) { + Text("Preparing data") + .font(.title) + .fontWeight(.bold) + .padding(.top, 10) + Text("Please wait") + .font(.title) + .fontWeight(.bold) + .padding(.top, 10) + } + } + .onAppear { + configureView() + state.animated.toggle() + state.runMigration() + } + .preference(key: PreferenceKeyAppLoading.self, value: state.loadingIsEnded) + } + } +} + +struct MyPreviewProvider_Previews: PreviewProvider { + static var previews: some View { + Migration.RootView(resolver: FreeAPSApp.resolver) + } +} diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift b/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift index eab8be450..288aeca99 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift @@ -15,7 +15,6 @@ extension PreferencesEditor { subscribeSetting(\.allowAnnouncements, on: $allowAnnouncements) { allowAnnouncements = $0 } subscribeSetting(\.insulinReqFraction, on: $insulinReqFraction) { insulinReqFraction = $0 } - subscribeSetting(\.skipBolusScreenAfterCarbs, on: $skipBolusScreenAfterCarbs) { skipBolusScreenAfterCarbs = $0 } subscribeSetting(\.units, on: $unitsIndex.map { $0 == 0 ? GlucoseUnits.mgdL : .mmolL }) { unitsIndex = $0 == .mgdL ? 0 : 1 @@ -173,7 +172,10 @@ extension PreferencesEditor { Field( displayName: "Bolus Increment", type: .decimal(keypath: \.bolusIncrement), - infoText: NSLocalizedString("Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1.", comment: "Bolus Increment"), + infoText: NSLocalizedString( + "Smallest SMB / SMB increment in oref0. Minimum amount for Medtronic pumps is 0.1 U, whereas for Omnipod it’s 0.05 U. The default value is 0.1.", + comment: "Bolus Increment" + ), settable: self ) ] diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift index aebfad189..04d458159 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift @@ -34,8 +34,6 @@ extension PreferencesEditor { Text("Recommended Insulin Fraction") DecimalTextField("", value: $state.insulinReqFraction, formatter: formatter) } - - Toggle("Skip Bolus screen after carbs", isOn: $state.skipBolusScreenAfterCarbs) } ForEach(state.sections.indexed(), id: \.1.id) { sectionIndex, section in diff --git a/FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift b/FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift index 6383c73bf..2651eac28 100644 --- a/FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift +++ b/FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift @@ -19,7 +19,8 @@ extension Settings { buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" - subscribeSetting(\.animatedBackground, on: $animatedBackground) { animatedBackground = $0 } + // Disabled until next holidays +// subscribeSetting(\.animatedBackground, on: $animatedBackground) { animatedBackground = $0 } } func logItems() -> [URL] { diff --git a/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift b/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift index c05825f58..97df27dee 100644 --- a/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift +++ b/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift @@ -20,6 +20,7 @@ extension Settings { Section(header: Text("Services")) { Text("Nightscout").navigationLink(to: .nighscoutConfig, from: self) + Text("LibreView").navigationLink(to: .libreViewConfig, from: self) Text("CGM").navigationLink(to: .cgm, from: self) if HKHealthStore.isHealthDataAvailable() { Text("Apple Health").navigationLink(to: .healthkit, from: self) @@ -40,6 +41,9 @@ extension Settings { Section(header: Text("Developer")) { Toggle("Debug options", isOn: $state.debugOptions) if state.debugOptions { + Group { + Text("Garmin watch").navigationLink(to: .garmin, from: self) + } Group { Text("Preferences") .navigationLink(to: .configEditor(file: OpenAPS.Settings.preferences), from: self) @@ -101,9 +105,10 @@ extension Settings { } } - Section { - Toggle("Animated Background", isOn: $state.animatedBackground) - } + // Disabled until next holidays +// Section { +// Toggle("Animated Background", isOn: $state.animatedBackground) +// } Section { Text("Share logs") diff --git a/FreeAPS/Sources/Modules/Snooze/View/SnoozeRootView.swift b/FreeAPS/Sources/Modules/Snooze/View/SnoozeRootView.swift index 2b4ea9efe..eddfacb4b 100644 --- a/FreeAPS/Sources/Modules/Snooze/View/SnoozeRootView.swift +++ b/FreeAPS/Sources/Modules/Snooze/View/SnoozeRootView.swift @@ -98,7 +98,7 @@ extension Snooze { private var snoozePicker: some View { VStack { Picker(selection: $selectedInterval, label: Text("Strength")) { - ForEach(0 ..< pickerTimes.count) { + ForEach(0 ..< pickerTimes.count, id: \.self) { Text(formatInterval(self.pickerTimes[$0])) } } diff --git a/FreeAPS/Sources/Router/Screen.swift b/FreeAPS/Sources/Router/Screen.swift index d79260c0b..543b2461d 100644 --- a/FreeAPS/Sources/Router/Screen.swift +++ b/FreeAPS/Sources/Router/Screen.swift @@ -7,6 +7,7 @@ enum Screen: Identifiable, Hashable { case settings case configEditor(file: String) case nighscoutConfig + case libreViewConfig case pumpConfig case pumpSettingsEditor case basalProfileEditor @@ -26,6 +27,8 @@ enum Screen: Identifiable, Hashable { case calibrations case notificationsConfig case snooze + case migration + case garmin var id: Int { String(reflecting: self).hashValue } } @@ -43,6 +46,8 @@ extension Screen { ConfigEditor.RootView(resolver: resolver, file: file) case .nighscoutConfig: NightscoutConfig.RootView(resolver: resolver) + case .libreViewConfig: + LibreViewConfig.RootView(resolver: resolver) case .pumpConfig: PumpConfig.RootView(resolver: resolver) case .pumpSettingsEditor: @@ -81,6 +86,10 @@ extension Screen { NotificationsConfig.RootView(resolver: resolver) case .snooze: Snooze.RootView(resolver: resolver) + case .migration: + Migration.RootView(resolver: resolver) + case .garmin: + GarminConfig.RootView(resolver: resolver) } } diff --git a/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift b/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift index 869ea4df1..31729cd60 100644 --- a/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift +++ b/FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift @@ -3,31 +3,38 @@ import Foundation import HealthKit import Swinject -protocol HealthKitManager: GlucoseSource { +protocol HealthKitManager: GlucoseSource, CarbsSource { /// Check all needed permissions /// Return false if one or more permissions are deny or not choosen var areAllowAllPermissions: Bool { get } /// Check availability to save data of BG type to Health store func checkAvailabilitySaveBG() -> Bool + /// Check availability to save data of Carbs type to Health store + func checkAvailabilitySaveCarbs() -> Bool /// Requests user to give permissions on using HealthKit func requestPermission(completion: ((Bool, Error?) -> Void)?) /// Save blood glucose to Health store (dublicate of bg will ignore) func saveIfNeeded(bloodGlucose: [BloodGlucose]) + /// Save carbs to Health store (dublicate of bg will ignore) + func saveIfNeeded(carbs: [CarbsEntry]) /// Create observer for data passing beetwen Health Store and FreeAPS func createObserver() /// Enable background delivering objects from Apple Health to FreeAPS func enableBackgroundDelivery() /// Delete glucose with syncID - func deleteGlucise(syncID: String) + func deleteCarbs(syncID: String) + /// Delete glucose with syncID + func deleteGlucose(syncID: String) } final class BaseHealthKitManager: HealthKitManager, Injectable { private enum Config { // unwraped HKObjects - static var permissions: Set { Set([healthBGObject].compactMap { $0 }) } + static var permissions: Set { Set([healthBGObject, healthCarbObject].compactMap { $0 }) } // link to object in HealthKit static let healthBGObject = HKObjectType.quantityType(forIdentifier: .bloodGlucose) + static let healthCarbObject = HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates) // Meta-data key of FreeASPX data in HealthStore static let freeAPSMetaKey = "fromFreeAPSX" @@ -36,25 +43,44 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { @Injected() private var glucoseStorage: GlucoseStorage! @Injected() private var healthKitStore: HKHealthStore! @Injected() private var settingsManager: SettingsManager! + @Injected() private var broadcaster: Broadcaster! private let processQueue = DispatchQueue(label: "BaseHealthKitManager.processQueue") private var lifetime = Lifetime() - // BG that will be return Publisher + // BG that will be return Publisher (GlucoseSource protocol) @SyncAccess @Persisted(key: "BaseHealthKitManager.newGlucose") private var newGlucose: [BloodGlucose] = [] + // Carbs that will be return Publisher (CarbsSource protocol) + @SyncAccess @Persisted(key: "BaseHealthKitManager.newCarbs") private var newCarbs: [CarbsEntry] = [] // last anchor for HKAnchoredQuery + // BG private var lastBloodGlucoseQueryAnchor: HKQueryAnchor? { set { - persistedAnchor = try? NSKeyedArchiver.archivedData(withRootObject: newValue as Any, requiringSecureCoding: false) + persistedBGAnchor = try? NSKeyedArchiver.archivedData(withRootObject: newValue as Any, requiringSecureCoding: false) + } + get { + guard let data = persistedBGAnchor else { return nil } + return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor + } + } + + @Persisted(key: "HealthKitManagerAnchor") private var persistedBGAnchor: Data? = nil + // Carbs + private var lastCarbsQueryAnchor: HKQueryAnchor? { + set { + persistedCarbsAnchor = try? NSKeyedArchiver.archivedData( + withRootObject: newValue as Any, + requiringSecureCoding: false + ) } get { - guard let data = persistedAnchor else { return nil } + guard let data = persistedCarbsAnchor else { return nil } return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor } } - @Persisted(key: "HealthKitManagerAnchor") private var persistedAnchor: Data? = nil + @Persisted(key: "HealthKitManagerAnchor_Carbs") private var persistedCarbsAnchor: Data? = nil var isAvailableOnCurrentDevice: Bool { HKHealthStore.isHealthDataAvailable() @@ -67,7 +93,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } // NSPredicate, which use during load increment BG from Health store - private var loadBGPredicate: NSPredicate { + private var loadHealthDataPredicate: NSPredicate { // loading only daily bg let predicateByStartDate = HKQuery.predicateForSamples( withStart: Date().addingTimeInterval(-1.days.timeInterval), @@ -93,6 +119,11 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { createObserver() enableBackgroundDelivery() debug(.service, "HealthKitManager did create") + subscribe() + } + + private func subscribe() { + broadcaster.register(CarbsObserver.self, observer: self) } func checkAvailabilitySave(objectTypeToHealthStore: HKObjectType) -> Bool { @@ -103,6 +134,10 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { Config.healthBGObject.map { checkAvailabilitySave(objectTypeToHealthStore: $0) } ?? false } + func checkAvailabilitySaveCarbs() -> Bool { + Config.healthCarbObject.map { checkAvailabilitySave(objectTypeToHealthStore: $0) } ?? false + } + func requestPermission(completion: ((Bool, Error?) -> Void)? = nil) { guard isAvailableOnCurrentDevice else { completion?(false, HKError.notAvailableOnCurrentDevice) @@ -118,6 +153,42 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } } + func saveIfNeeded(carbs: [CarbsEntry]) { + guard settingsManager.settings.useAppleHealth, + let sampleType = Config.healthCarbObject, + checkAvailabilitySave(objectTypeToHealthStore: sampleType), + carbs.isNotEmpty + else { return } + + func save(samples: [HKSample]) { + let sampleIDs = samples.compactMap(\.syncIdentifier) + let samplesToSave = carbs + .filter { !sampleIDs.contains($0.id) } + .filter { $0.enteredBy != CarbsEntry.applehealth } + .map { + HKQuantitySample( + type: sampleType, + quantity: HKQuantity(unit: .gram(), doubleValue: Double($0.carbs)), + start: $0.createdAt, + end: $0.createdAt, + metadata: [ + HKMetadataKeyExternalUUID: $0.id, + HKMetadataKeySyncIdentifier: $0.id, + HKMetadataKeySyncVersion: 1, + Config.freeAPSMetaKey: true + ] + ) + } + + healthKitStore.save(samplesToSave) { _, _ in } + } + + loadSamplesFromHealth(sampleType: sampleType, withIDs: carbs.map(\.id)) + .receive(on: processQueue) + .sink(receiveValue: save) + .store(in: &lifetime) + } + func saveIfNeeded(bloodGlucose: [BloodGlucose]) { guard settingsManager.settings.useAppleHealth, let sampleType = Config.healthBGObject, @@ -153,24 +224,30 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { .store(in: &lifetime) } + // MARK: - Observers & Background data delivery + func createObserver() { guard settingsManager.settings.useAppleHealth else { return } - guard let bgType = Config.healthBGObject else { + createBGObserver() + createCarbsObserver() + } + + private func createBGObserver() { + guard let type = Config.healthBGObject else { warning(.service, "Can not create HealthKit Observer, because unable to get the Blood Glucose type") return } - - let query = HKObserverQuery(sampleType: bgType, predicate: nil) { [weak self] _, _, observerError in + let query = HKObserverQuery(sampleType: type, predicate: nil) { [weak self] _, _, observerError in guard let self = self else { return } - debug(.service, "Execute HelathKit observer query for loading increment samples") + debug(.service, "Execute HealthKit observer query for loading increment samples") guard observerError == nil else { - warning(.service, "Error during execution of HelathKit Observer's query", error: observerError!) + warning(.service, "Error during execution of HealthKit Observer's query", error: observerError!) return } - if let incrementQuery = self.getBloodGlucoseHKQuery(predicate: self.loadBGPredicate) { - debug(.service, "Create increment query") + if let incrementQuery = self.getBloodGlucoseHKQuery(predicate: self.loadHealthDataPredicate) { + debug(.service, "Create increment query for loading bg") self.healthKitStore.execute(incrementQuery) } } @@ -178,6 +255,28 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { debug(.service, "Create Observer for Blood Glucose") } + private func createCarbsObserver() { + guard let type = Config.healthCarbObject else { + warning(.service, "Can not create HealthKit Observer, because unable to get the Carbs type") + return + } + let query = HKObserverQuery(sampleType: type, predicate: nil) { [weak self] _, _, observerError in + guard let self = self else { return } + debug(.service, "Execute HealthKit observer query for loading increment samples") + guard observerError == nil else { + warning(.service, "Error during execution of HealthKit Observer's query", error: observerError!) + return + } + + if let incrementQuery = self.getCarbsHKQuery(predicate: self.loadHealthDataPredicate) { + debug(.service, "Create increment query for loading carbs") + self.healthKitStore.execute(incrementQuery) + } + } + healthKitStore.execute(query) + debug(.service, "Create Observer for Carbs") + } + func enableBackgroundDelivery() { guard settingsManager.settings.useAppleHealth else { healthKitStore.disableAllBackgroundDelivery { _, _ in } @@ -193,10 +292,26 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { healthKitStore.enableBackgroundDelivery(for: bgType, frequency: .immediate) { status, error in guard error == nil else { - warning(.service, "Can not enable background delivery", error: error) + warning(.service, "Can not enable background delivery for bg", error: error) + return + } + debug(.service, "Background bg delivery status is \(status)") + } + + guard let carbsType = Config.healthCarbObject else { + warning( + .service, + "Can not create background delivery, because unable to get the Carbs type" + ) + return + } + + healthKitStore.enableBackgroundDelivery(for: carbsType, frequency: .immediate) { status, error in + guard error == nil else { + warning(.service, "Can not enable background delivery for carbs", error: error) return } - debug(.service, "Background delivery status is \(status)") + debug(.service, "Background carbs delivery status is \(status)") } } @@ -234,7 +349,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { ) { [weak self] _, addedObjects, _, anchor, _ in guard let self = self else { return } self.processQueue.async { - debug(.service, "AnchoredQuery did execute") + debug(.service, "BG AnchoredQuery did execute") self.lastBloodGlucoseQueryAnchor = anchor @@ -242,22 +357,80 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { if let bgSamples = addedObjects as? [HKQuantitySample], bgSamples.isNotEmpty { - self.prepareSamplesToPublisherFetch(bgSamples) + self.prepareBGSamplesToPublisherFetch(bgSamples) } } } return query } - private func prepareSamplesToPublisherFetch(_ samples: [HKQuantitySample]) { + private func getCarbsHKQuery(predicate: NSPredicate) -> HKQuery? { + guard let sampleType = Config.healthCarbObject else { return nil } + + let query = HKAnchoredObjectQuery( + type: sampleType, + predicate: predicate, + anchor: lastCarbsQueryAnchor, + limit: HKObjectQueryNoLimit + ) { [weak self] _, addedObjects, _, anchor, _ in + guard let self = self else { return } + self.processQueue.async { + debug(.service, "Carbs AnchoredQuery did execute") + + self.lastCarbsQueryAnchor = anchor + + // Added objects + if let samples = addedObjects as? [HKQuantitySample], + samples.isNotEmpty + { + self.prepareCarbsSamplesToPublisherFetch(samples) + } + } + } + return query + } + + private func prepareCarbsSamplesToPublisherFetch(_ samples: [HKQuantitySample]) { dispatchPrecondition(condition: .onQueue(processQueue)) - debug(.service, "Start preparing samples: \(String(describing: samples))") + debug(.service, "Start preparing carbs samples: \(String(describing: samples))") + + newCarbs += samples + .compactMap { sample -> HealthKitCarbsSample? in + let fromFAX = sample.metadata?[Config.freeAPSMetaKey] as? Bool ?? false + guard !fromFAX else { return nil } + return HealthKitCarbsSample( + healthKitId: sample.uuid.uuidString, + date: sample.startDate, + carbs: Decimal(round(sample.quantity.doubleValue(for: .gram()))) + ) + } + .map { sample in + CarbsEntry( + id: sample.healthKitId, + createdAt: sample.date, + carbs: sample.carbs, + enteredBy: "applehealth" + ) + } + .filter { $0.createdAt >= Date().addingTimeInterval(-1.days.timeInterval) } + + newCarbs = newCarbs.removeDublicates() + + debug( + .service, + "Current Carbs.Type objects will be send from Publisher during fetch: \(String(describing: newCarbs))" + ) + } + + private func prepareBGSamplesToPublisherFetch(_ samples: [HKQuantitySample]) { + dispatchPrecondition(condition: .onQueue(processQueue)) + debug(.service, "Start preparing bg samples: \(String(describing: samples))") newGlucose += samples - .compactMap { sample -> HealthKitSample? in + .compactMap { sample -> HealthKitBGSample? in let fromFAX = sample.metadata?[Config.freeAPSMetaKey] as? Bool ?? false guard !fromFAX else { return nil } - return HealthKitSample( + return HealthKitBGSample( healthKitId: sample.uuid.uuidString, date: sample.startDate, glucose: Int(round(sample.quantity.doubleValue(for: .milligramsPerDeciliter))) @@ -287,6 +460,65 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { ) } + // MARK: - Carbs source + + func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never> { + Future { [weak self] promise in + guard let self = self else { + promise(.success([])) + return + } + + self.processQueue.async { + debug(.service, "Start fetching carbs from HealthKitManager") + guard self.settingsManager.settings.useAppleHealth else { + debug(.service, "HealthKitManager cant return any data, because useAppleHealth option is disable") + promise(.success([])) + return + } + + // Remove old Carbs + self.newCarbs = self.newCarbs + .filter { $0.createdAt >= Date().addingTimeInterval(-1.days.timeInterval) } + // Get actual Carbs (beetwen Date() - 1 day and Date()) + let actualCarbs = self.newCarbs + .filter { $0.createdAt <= Date() } + // Update newCarbs + self.newCarbs = self.newCarbs + .filter { !actualCarbs.contains($0) } + + debug(.service, "Actual carbs is \(actualCarbs)") + + debug(.service, "Current state of newCarbs is \(self.newCarbs)") + + promise(.success(actualCarbs)) + } + } + .eraseToAnyPublisher() + } + + func deleteCarbs(syncID: String) { + guard settingsManager.settings.useAppleHealth, + let sampleType = Config.healthCarbObject, + checkAvailabilitySave(objectTypeToHealthStore: sampleType) + else { return } + + processQueue.async { + let predicate = HKQuery.predicateForObjects( + withMetadataKey: HKMetadataKeySyncIdentifier, + operatorType: .equalTo, + value: syncID + ) + + self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in + guard let error = error else { return } + warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error) + } + } + } + + // MARK: - Glucose source + func fetch() -> AnyPublisher<[BloodGlucose], Never> { Future { [weak self] promise in guard let self = self else { @@ -295,7 +527,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } self.processQueue.async { - debug(.service, "Start fetching HealthKitManager") + debug(.service, "Start fetching bloodGlucose from HealthKitManager") guard self.settingsManager.settings.useAppleHealth else { debug(.service, "HealthKitManager cant return any data, because useAppleHealth option is disable") promise(.success([])) @@ -322,7 +554,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { .eraseToAnyPublisher() } - func deleteGlucise(syncID: String) { + func deleteGlucose(syncID: String) { guard settingsManager.settings.useAppleHealth, let sampleType = Config.healthBGObject, checkAvailabilitySave(objectTypeToHealthStore: sampleType) @@ -343,6 +575,12 @@ final class BaseHealthKitManager: HealthKitManager, Injectable { } } +extension BaseHealthKitManager: CarbsObserver { + func carbsDidUpdate(_ carbs: [CarbsEntry]) { + saveIfNeeded(carbs: carbs) + } +} + enum HealthKitPermissionRequestStatus { case needRequest case didRequest diff --git a/FreeAPS/Sources/Services/Migration/MigrationManager.swift b/FreeAPS/Sources/Services/Migration/MigrationManager.swift new file mode 100644 index 000000000..fc7595f40 --- /dev/null +++ b/FreeAPS/Sources/Services/Migration/MigrationManager.swift @@ -0,0 +1,55 @@ +import Combine +import Foundation +import SwiftUI +import Swinject + +protocol MigrationManager { + var appInfo: AppInfo { get } + // 'true' when app run first time + var isFirstExecute: Bool { get } + + func checkMigrationNeededRun(_: MigrationWorkItem, startAtVersion version: String) -> Bool + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) + + init(resolver: Resolver) +} + +class BaseMigrationManager: MigrationManager { + var isFirstExecute: Bool { + // check first app execution by preferences.json file + // if file doesn't exist - the first execution + !Disk.exists(OpenAPS.FreeAPS.settings, in: .documents) + } + + var appInfo: AppInfo + + private var resolver: Resolver + + required init(resolver: Resolver) { + self.resolver = resolver + appInfo = resolver.resolve(AppInfo.self)! + } + + func checkMigrationNeededRun(_ migrationWorkItem: MigrationWorkItem, startAtVersion version: String) -> Bool { + // if current app version >= version of migration needed + guard appInfo.currentVersion >= version else { return false } + // if migration need to run each app execution + if migrationWorkItem.repeatEachTime { return true } + // if it first run of app + guard !isFirstExecute else { return false } + // if migration did run in past + guard UserDefaults.standard.optionalBool(forKey: migrationWorkItem.uniqueIdentifier) == nil else { return false } + return true + } + + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) { + debug(.businessLogic, "Try to execute migration on version \(version)") + if checkMigrationNeededRun(workItem, startAtVersion: version) { + debug(.businessLogic, "Start migration \(workItem.uniqueIdentifier)") + workItem.migrationHandler(appInfo) + UserDefaults.standard.set(true, forKey: workItem.uniqueIdentifier) + } else { + debug(.businessLogic, "Skip migration \(workItem.uniqueIdentifier)") + } + } +} diff --git a/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift new file mode 100644 index 000000000..cda0e9439 --- /dev/null +++ b/FreeAPS/Sources/Services/Migration/MigrationPublisher.swift @@ -0,0 +1,50 @@ +import Combine +import Foundation +import SwiftUI + +extension Publishers { + static func getMigrationPublisher(fromMigrationManager manager: MigrationManager) -> MigrationPublisher { + MigrationPublisher(manager) + } + + class MigrationSubscription: Subscription where S.Input == AppInfo, S.Failure == Never { + private var manager: MigrationManager + private var subscriber: S? + + init(_ manager: MigrationManager, subscriber: S) { + self.manager = manager + self.subscriber = subscriber + } + + func cancel() { + subscriber = nil + } + + func request(_: Subscribers.Demand) { + _ = subscriber?.receive(manager.appInfo) + subscriber?.receive(completion: .finished) + return + } + } + + struct MigrationPublisher: Publisher { + typealias Output = AppInfo + typealias Failure = Never + + private var manager: MigrationManager + + init(_ manager: MigrationManager) { + self.manager = manager + } + + func receive(subscriber: S) where S: Subscriber, Never == S.Failure, AppInfo == S.Input { + let subscription = MigrationSubscription(manager, subscriber: subscriber) + subscriber.receive(subscription: subscription) + } + + func migrate(startAtVersion version: String, _ workItem: MigrationWorkItem) -> Self { + manager.migrate(startAtVersion: version, workItem) + return self + } + } +} diff --git a/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift new file mode 100644 index 000000000..0b959d122 --- /dev/null +++ b/FreeAPS/Sources/Services/Migration/MigrationWorkItem.swift @@ -0,0 +1,41 @@ +import Foundation + +protocol MigrationWorkItem { + // If true then migration will run each time while app is loading + var repeatEachTime: Bool { get } + // Unique identifier to store migration execute flag in UserDefaults + var uniqueIdentifier: String { get } + // Migration task + func migrationHandler(_: AppInfo) +} + +class MigrationWorkExample: MigrationWorkItem { + private(set) var repeatEachTime: Bool = false + private(set) var uniqueIdentifier: String = "Migration.MigrationWorkExample" + func migrationHandler(_: AppInfo) { + debug(.businessLogic, "Migration MigrationWorkExample will start") + } +} + +// MARK: - Migration carbs at 0.2.6 + +// New CarbsEntry class (with new id property) was create. +// This work item add new property to carbs in carbs.json + +class MigrationCarbs: MigrationWorkItem { + private(set) var repeatEachTime: Bool = false + private(set) var uniqueIdentifier: String = "Migration.MigrationCarbs" + func migrationHandler(_: AppInfo) { + let resolver = FreeAPSApp.resolver + let fileStorage = resolver.resolve(FileStorage.self)! + let carbsStorage = resolver.resolve(CarbsStorage.self)! + guard let oldCarbs = fileStorage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntryTill026].self) else { return } + carbsStorage.storeCarbs(convert(carbs: oldCarbs)) + } + + private func convert(carbs: [CarbsEntryTill026]) -> [CarbsEntry] { + carbs.map { oldCarb in + CarbsEntry(createdAt: oldCarb.createdAt, carbs: oldCarb.carbs, enteredBy: oldCarb.enteredBy) + } + } +} diff --git a/FreeAPS/Sources/Services/Network/LibreLinkManager.swift b/FreeAPS/Sources/Services/Network/LibreLinkManager.swift new file mode 100644 index 000000000..2a31560d4 --- /dev/null +++ b/FreeAPS/Sources/Services/Network/LibreLinkManager.swift @@ -0,0 +1,470 @@ +import Combine +import Foundation +import Swinject +import UIKit + +protocol LibreLinkManager { + func createConnection(url: URL, username: String, password: String) -> AnyPublisher + func uploadIfNeeded() + func uploadGlucose(url: URL, token: String, from: TimeInterval, to: TimeInterval) + -> AnyPublisher +} + +enum LibreLinkManagerError: LocalizedError { + case wrongToken + case wrongSettings + case wrongPasswordOrLogin + case wrongLastUploadDate + case notGlucoseToUpload + case notAllowUploadData + case tooEarlyToUpload + case error(String) + + var errorDescription: String? { + switch self { + case .wrongToken: + return "Wrong connection's token" + case .wrongSettings: + return "Wrong connection's settings" + case .wrongPasswordOrLogin: + return "Wrong password or login" + case .wrongLastUploadDate: + return "Last upload date to LibreLink is more, that current date. Try later or check preferences" + case .notGlucoseToUpload: + return "Have not new glucose to upload" + case .notAllowUploadData: + return "Not allow upload data to LibreView" + case .tooEarlyToUpload: + return "Too early to upload data" + case let .error(description): + return description + } + } +} + +class BaseLibreLinkManager: Injectable { + @Injected() private var reachabilityManager: ReachabilityManager! + @Injected() private var settingsManager: SettingsManager! + @Injected() private var glucoseStorage: GlucoseStorage! + @Injected() private var keychain: Keychain! + + private let processQueue = DispatchQueue(label: "BaseLibreLinkManager.processQueue") + private let service = NetworkService() + + private var lifetime = Lifetime() + + enum Config { + static let authenticationPath = "/lsl/api/nisperson/getauthentication" + static let measurementPath = "lsl/api/measurements" + } + + init(resolver: Resolver) { + injectServices(resolver) + subscribe() + } + + private var isNetworkReachable: Bool { + reachabilityManager.isReachable + } + + private func subscribe() { + _ = reachabilityManager.startListening(onQueue: processQueue) { status in + debug(.librelink, "Network status: \(status)") + } + } +} + +extension BaseLibreLinkManager: LibreLinkManager { + func uploadIfNeeded() { + debug(.librelink, "Start uploading data to LibreLink") + do { + guard settingsManager.settings.libreViewLastAllowUploadGlucose else { + throw LibreLinkManagerError.notAllowUploadData + } + guard let server = LibreViewConfig.Server.byViewTag(settingsManager.settings.libreViewServer), + let url = server == .custom ? URL(string: settingsManager.settings.libreViewCustomServer) : + URL(string: "https://\(server.rawValue)") + else { + throw LibreLinkManagerError.wrongSettings + } + guard let token = keychain.getValue(String.self, forKey: LibreViewConfig.Config.lvTokenKey), token != "" else { + throw LibreLinkManagerError.wrongToken + } + let currentTimestamp = Date().timeIntervalSince1970 + let nextUploadTimeStamp = settingsManager.settings.libreViewLastUploadTimestamp + settingsManager.settings + .libreViewNextUploadDelta + guard currentTimestamp >= nextUploadTimeStamp else { + throw LibreLinkManagerError.tooEarlyToUpload + } + uploadGlucose( + url: url, + token: token, + from: settingsManager.settings.libreViewLastUploadTimestamp, + to: currentTimestamp + ) + .replaceError(with: false) + .sink { [weak self] uploadResult in + guard uploadResult else { return } + self?.settingsManager.settings.libreViewLastUploadTimestamp = currentTimestamp + } + .store(in: &lifetime) + debug(.librelink, "Upload to libreLink successfully ended") + } catch { + debug(.librelink, "Error during uploading data to LibreLink: \(error.localizedDescription)") + } + } + + func uploadGlucose( + url: URL, + token: String, + from lastUploadTimestamp: TimeInterval, + to currentTimestamp: TimeInterval + ) -> AnyPublisher { + debug(.librelink, "Start uploading glucose to LibreLink from \(lastUploadTimestamp) to \(currentTimestamp)") + guard token != "" else { + return Fail(error: LibreLinkManagerError.wrongToken).eraseToAnyPublisher() + } + + guard lastUploadTimestamp < currentTimestamp else { + return Fail(error: LibreLinkManagerError.wrongLastUploadDate).eraseToAnyPublisher() + } + let notUploadedGlucose = glucoseStorage.recent().filter { glucose in + glucose.dateString.timeIntervalSince1970 > lastUploadTimestamp + } + + guard notUploadedGlucose.isNotEmpty else { + return Fail(error: LibreLinkManagerError.notGlucoseToUpload).eraseToAnyPublisher() + } + + var request = URLRequest(url: url.appendingPathComponent(Config.measurementPath)) + let requestBody = MeasurementRequest( + token: token, + bg: notUploadedGlucose.compactMap { bgItem -> PreparedBloodGlucose? in + + guard let glucose = bgItem.glucose else { return nil } + + return PreparedBloodGlucose( + id: Int(bgItem.dateString.timeIntervalSince1970), + value: glucose, + date: bgItem.dateString, + trend: bgItem.direction ?? .none + ) + } + ) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpMethod = "POST" + request.httpBody = try! JSONCoding.encoder.encode(requestBody) + + return service.run(request) + .decode(type: MeasurementResponse.self, decoder: JSONDecoder()) + .tryMap { response -> MeasurementsUploadResult in + guard response.status == 0 else { + if response.status == 24 { throw LibreLinkManagerError.wrongPasswordOrLogin } + else { throw LibreLinkManagerError.error(response.reason ?? "Something was wrong") } + } + self.updateUploadTimestampDelta() + debug(.librelink, "Finish uploading data to LibreLink. Was upload \(notUploadedGlucose.count) bloodGlucose items") + return true + } + .eraseToAnyPublisher() + } + + func createConnection(url: URL, username: String, password: String) -> AnyPublisher { + var request = URLRequest(url: url.appendingPathComponent(Config.authenticationPath)) + let requestBody = NetworkConnectionRequest(username: username, password: password) + + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpMethod = "POST" + request.httpBody = try! JSONCoding.encoder.encode(requestBody) + + return service.run(request) + .decode(type: NetworkConnectionResponse.self, decoder: JSONDecoder()) + .tryMap { response in + guard response.status == 0, let responseBody = response.result else { + if response.status == 10 { throw LibreLinkManagerError.wrongToken } + else { throw LibreLinkManagerError.error(response.reason ?? "Something was wrong") } + } + return responseBody.userToken + } + .eraseToAnyPublisher() + } + + private func updateUploadTimestampDelta() { + guard let frequency = LibreViewConfig.UploadsFrequency(rawValue: settingsManager.settings.libreViewFrequenceUploads) + else { + settingsManager.settings.libreViewFrequenceUploads = 0 + settingsManager.settings.libreViewNextUploadDelta = 0 + return + } + settingsManager.settings.libreViewNextUploadDelta = frequency.secondsToNextUpload + } +} + +// MARK: - Subtypes + +typealias LibreLinkToken = String +typealias MeasurementsUploadResult = Bool + +extension BaseLibreLinkManager { + // MARK: Local models + + struct PreparedBloodGlucose { + var id: Int + var value: Int + var date: Date + var trend: BloodGlucose.Direction + } + + // MARK: Connection + + struct NetworkConnectionRequest: Codable { + var username: String + var password: String + var domain = "Libreview" + var gatewayType = "FSLibreLink.iOS" + var deviceID = UIDevice.current.identifierForVendor!.uuidString + var setDevice = true + } + + struct NetworkConnectionResponse: Codable { + let status: Int + let reason: String? + let result: Result? + + struct Result: Codable { + let userToken, accountID, userName, firstName: String + let lastName, middleInitial, email, country: String + let culture: String + let timezone: String? + let dateOfBirth: String + let backupFileExists, isHCP, validated, needToAcceptPolicies: Bool + let communicationLanguage, uiLanguage: String + let supportedDevices: String? + let created, guardianLastName, guardianFirstName, domainData: String + + enum CodingKeys: String, CodingKey { + case userToken = "UserToken" + case accountID = "AccountId" + case userName = "UserName" + case firstName = "FirstName" + case lastName = "LastName" + case middleInitial = "MiddleInitial" + case email = "Email" + case country = "Country" + case culture = "Culture" + case timezone = "Timezone" + case dateOfBirth = "DateOfBirth" + case backupFileExists = "BackupFileExists" + case isHCP = "IsHCP" + case validated = "Validated" + case needToAcceptPolicies = "NeedToAcceptPolicies" + case communicationLanguage = "CommunicationLanguage" + case uiLanguage = "UiLanguage" + case supportedDevices = "SupportedDevices" + case created = "Created" + case guardianLastName = "GuardianLastName" + case guardianFirstName = "GuardianFirstName" + case domainData = "DomainData" + } + } + } + + // MARK: Measurement + + struct MeasurementRequest: Encodable { + let gatewayType = "FSLibreLink.iOS" + let domain = "Libreview" + let userToken: String + let deviceData: DeviceData + + init(token: String, bg: [PreparedBloodGlucose]) { + userToken = token + deviceData = DeviceData(bg: bg) + } + + struct DeviceData: Encodable { + let header = Header() + let measurementLog: MeasurementLog + + init(bg: [PreparedBloodGlucose]) { + measurementLog = MeasurementLog(bg: bg) + } + } + + struct Header: Encodable { + let device = Device() + } + + struct Device: Encodable { + let hardwareDescriptor = "iPhone14,3" + let osVersion = "16.0" + let modelName = "com.abbott.librelink.ru" + let osType = "iOS" + let uniqueIdentifier = UIDevice.current.identifierForVendor!.uuidString + let hardwareName = "iPhone" + } + + struct MeasurementLog: Encodable { + let bloodGlucoseEntries = [String]() + let capabilities = [ + "scheduledContinuousGlucose", + "unscheduledContinuousGlucose", + "bloodGlucose", + "insulin", + "food", + "generic-com.abbottdiabetescare.informatics.exercise", + "generic-com.abbottdiabetescare.informatics.customnote", + "generic-com.abbottdiabetescare.informatics.ondemandalarm.low", + "generic-com.abbottdiabetescare.informatics.ondemandalarm.high", + "generic-com.abbottdiabetescare.informatics.ondemandalarm.projectedlow", + "generic-com.abbottdiabetescare.informatics.ondemandalarm.projectedhigh", + "generic-com.abbottdiabetescare.informatics.sensorstart", + "generic-com.abbottdiabetescare.informatics.error", + "generic-com.abbottdiabetescare.informatics.isfGlucoseAlarm", + "generic-com.abbottdiabetescare.informatics.alarmSetting" + ] + let scheduledContinuousGlucoseEntries: [ScheduledContinuousGlucoseEntry] + let insulinEntries = [String]() + let foodEntries = [String]() + let unscheduledContinuousGlucoseEntries: [UnscheduledContinuousGlucoseEntry] + + init(bg: [PreparedBloodGlucose]) { + scheduledContinuousGlucoseEntries = bg.map { bgItem in ScheduledContinuousGlucoseEntry(bg: bgItem) } + if let lastBG = bg.last { + unscheduledContinuousGlucoseEntries = [UnscheduledContinuousGlucoseEntry(bg: lastBG)] + } else { + unscheduledContinuousGlucoseEntries = [] + } + } + } + + struct ScheduledContinuousGlucoseEntry: Encodable { + let extendedProperties: ExtendedScheduledProperties + let recordNumber: Int + let timestamp: String + let valueInMgPerDL: Int + + init(bg: PreparedBloodGlucose) { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withTimeZone] + formatter.timeZone = TimeZone.current + + recordNumber = bg.id + timestamp = formatter.string(from: bg.date) + valueInMgPerDL = Int(bg.value) + + formatter.timeZone = TimeZone(secondsFromGMT: 0) + extendedProperties = ExtendedScheduledProperties( + bgValue: bg.value, + factoryTimestamp: formatter.string(from: bg.date) + ) + } + } + + struct ExtendedScheduledProperties: Encodable { + let highOutOfRange: String + let canMerge = "true" + let isFirstAfterTimeChange = false + let factoryTimestamp: String + let lowOutOfRange: String + + init(bgValue: Int, factoryTimestamp: String) { + if bgValue <= 70 { + highOutOfRange = "false" + lowOutOfRange = "true" + } else if bgValue >= 180 { + highOutOfRange = "true" + lowOutOfRange = "false" + } else { + highOutOfRange = "false" + lowOutOfRange = "false" + } + self.factoryTimestamp = factoryTimestamp + } + } + + struct UnscheduledContinuousGlucoseEntry: Encodable { + let extendedProperties: ExtendedUnscheduledProperties + let recordNumber: Int + let timestamp: String + let valueInMgPerDL: Int + + init(bg: PreparedBloodGlucose) { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withTimeZone] + formatter.timeZone = TimeZone.current + + recordNumber = bg.id + timestamp = formatter.string(from: bg.date) + valueInMgPerDL = Int(bg.value) + + formatter.timeZone = TimeZone(secondsFromGMT: 0) + extendedProperties = ExtendedUnscheduledProperties( + bgValue: bg.value, + factoryTimestamp: formatter.string(from: bg.date), + direction: bg.trend + ) + } + } + + struct ExtendedUnscheduledProperties: Encodable { + let highOutOfRange: String + let isActionable = true + let trendArrow: String + let isFirstAfterTimeChange = false + let factoryTimestamp: String + let lowOutOfRange: String + + init(bgValue: Int, factoryTimestamp: String, direction: BloodGlucose.Direction) { + if bgValue <= 70 { + highOutOfRange = "false" + lowOutOfRange = "true" + } else if bgValue >= 180 { + highOutOfRange = "true" + lowOutOfRange = "false" + } else { + highOutOfRange = "false" + lowOutOfRange = "false" + } + self.factoryTimestamp = factoryTimestamp + switch direction { + case .doubleUp, + .singleUp, + .tripleUp: + trendArrow = "Rising" + case .flat, + .fortyFiveDown, + .fortyFiveUp: + trendArrow = "Stable" + case .doubleDown, + .singleDown, + .tripleDown: + trendArrow = "Falling" + case .none, + .notComputable, + .rateOutOfRange: + trendArrow = "Stable" + } + } + } + } + + struct MeasurementResponse: Decodable { + let status: Int + let result: Result? + let reason: String? + + struct Result: Decodable { + let uploadID: String? + let status: Int? + let measurementCounts: MeasurementCounts? + let itemCount: Int? + let createdDateTime, serialNumber: String? + } + + struct MeasurementCounts: Decodable { + let scheduledGlucoseCount, unScheduledGlucoseCount, bloodGlucoseCount, insulinCount: Int? + let genericCount, foodCount, ketoneCount, totalCount: Int? + } + } +} diff --git a/FreeAPS/Sources/Services/Network/NightscoutManager.swift b/FreeAPS/Sources/Services/Network/NightscoutManager.swift index c6b20767c..3a4d2ad3f 100644 --- a/FreeAPS/Sources/Services/Network/NightscoutManager.swift +++ b/FreeAPS/Sources/Services/Network/NightscoutManager.swift @@ -3,7 +3,7 @@ import Foundation import Swinject import UIKit -protocol NightscoutManager: GlucoseSource { +protocol NightscoutManager: GlucoseSource, CarbsSource { func fetchGlucose(since date: Date) -> AnyPublisher<[BloodGlucose], Never> func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never> func fetchTempTargets() -> AnyPublisher<[TempTarget], Never> diff --git a/FreeAPS/Sources/Services/WatchManager/GarminManager.swift b/FreeAPS/Sources/Services/WatchManager/GarminManager.swift new file mode 100644 index 000000000..88878f820 --- /dev/null +++ b/FreeAPS/Sources/Services/WatchManager/GarminManager.swift @@ -0,0 +1,187 @@ +import Combine +import ConnectIQ +import Foundation +import Swinject + +protocol GarminManager { + func selectDevices() -> AnyPublisher<[IQDevice], Never> + var devices: [IQDevice] { get } + func sendState(_ data: Data) + var stateRequet: (() -> (Data))? { get set } +} + +extension Notification.Name { + static let openFromGarminConnect = Notification.Name("Notification.Name.openFromGarminConnect") +} + +final class BaseGarminManager: NSObject, GarminManager, Injectable { + private enum Config { + static let watchfaceUUID = UUID(uuidString: "EC3420F6-027D-49B3-B45F-D81D6D3ED90A") + } + + private let connectIQ = ConnectIQ.sharedInstance() + + @Injected() private var notificationCenter: NotificationCenter! + + @Persisted(key: "BaseGarminManager.persistedDevices") private var persistedDevices: [CodableDevice] = [] + + private var watchfaces: [IQApp] = [] + + var stateRequet: (() -> (Data))? + + private let stateSubject = PassthroughSubject() + + private(set) var devices: [IQDevice] = [] { + didSet { + persistedDevices = devices.map(CodableDevice.init) + watchfaces = [] + devices.forEach { device in + connectIQ?.register(forDeviceEvents: device, delegate: self) + let watchfaceApp = IQApp( + uuid: Config.watchfaceUUID, + store: UUID(), + device: device + ) + watchfaces.append(watchfaceApp!) + connectIQ?.register(forAppMessages: watchfaceApp, delegate: self) + } + } + } + + private var lifetime = Lifetime() + private var selectPromise: Future<[IQDevice], Never>.Promise? + + init(resolver: Resolver) { + super.init() + connectIQ?.initialize(withUrlScheme: "freeaps-x", uiOverrideDelegate: self) + injectServices(resolver) + restoreDevices() + subscribeToOpenFromGarminConnect() + setupApplications() + subscribeState() + } + + private func subscribeToOpenFromGarminConnect() { + notificationCenter + .publisher(for: .openFromGarminConnect) + .sink { notification in + guard let url = notification.object as? URL else { return } + self.parseDevicesFor(url: url) + } + .store(in: &lifetime) + } + + private func subscribeState() { + func sendToWatchface(state: NSDictionary) { + watchfaces.forEach { app in + connectIQ?.getAppStatus(app) { status in + guard status?.isInstalled ?? false else { + debug(.service, "Garmin: watchface app not installed") + return + } + debug(.service, "Garmin: sending message to watchface") + self.sendMessage(state, to: app) + } + } + } + + stateSubject + .throttle(for: .seconds(10), scheduler: DispatchQueue.main, latest: true) + .sink { state in + sendToWatchface(state: state) + } + .store(in: &lifetime) + } + + private func restoreDevices() { + devices = persistedDevices.map(\.iqDevice) + } + + private func parseDevicesFor(url: URL) { + devices = connectIQ?.parseDeviceSelectionResponse(from: url) as? [IQDevice] ?? [] + selectPromise?(.success(devices)) + selectPromise = nil + } + + private func setupApplications() { + devices.forEach { _ in + } + } + + func selectDevices() -> AnyPublisher<[IQDevice], Never> { + Future { promise in + self.selectPromise = promise + self.connectIQ?.showDeviceSelection() + } + .timeout(120, scheduler: DispatchQueue.main) + .replaceEmpty(with: []) + .eraseToAnyPublisher() + } + + func sendState(_ data: Data) { + guard let object = try? JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary else { + return + } + stateSubject.send(object) + } + + private func sendMessage(_ msg: NSDictionary, to app: IQApp) { + connectIQ?.sendMessage(msg, to: app, progress: { sent, all in + debug(.service, "Garmin: sending progress: \(Int(Double(sent) / Double(all) * 100)) %") + }, completion: { result in + if result == .success { + debug(.service, "Garmin: message sent") + } else { + debug(.service, "Garmin: message failed") + } + }) + } +} + +extension BaseGarminManager: IQUIOverrideDelegate { + func needsToInstallConnectMobile() {} +} + +extension BaseGarminManager: IQDeviceEventDelegate { + func deviceStatusChanged(_ device: IQDevice, status: IQDeviceStatus) { + switch status { + case .invalidDevice: + debug(.service, "Garmin: invalidDevice, Device: \(device.uuid!)") + case .bluetoothNotReady: + debug(.service, "Garmin: bluetoothNotReady, Device: \(device.uuid!)") + case .notFound: + debug(.service, "Garmin: notFound, Device: \(device.uuid!)") + case .notConnected: + debug(.service, "Garmin: notConnected, Device: \(device.uuid!)") + case .connected: + debug(.service, "Garmin: connected, Device: \(device.uuid!)") + @unknown default: + debug(.service, "Garmin: unknown state, Device: \(device.uuid!)") + } + } +} + +extension BaseGarminManager: IQAppMessageDelegate { + func receivedMessage(_ message: Any, from app: IQApp) { + print("ASDF: got message: \(message) from app: \(app.uuid!)") + if let status = message as? String, status == "status", let watchState = stateRequet?() { + sendState(watchState) + } + } +} + +struct CodableDevice: Codable, Equatable { + let id: UUID + let modelName: String + let friendlyName: String + + init(iqDevice: IQDevice) { + id = iqDevice.uuid + modelName = iqDevice.modelName + friendlyName = iqDevice.modelName + } + + var iqDevice: IQDevice { + IQDevice(id: id, modelName: modelName, friendlyName: friendlyName) + } +} diff --git a/FreeAPS/Sources/Services/WatchManager/WatchManager.swift b/FreeAPS/Sources/Services/WatchManager/WatchManager.swift index 6ef13a5b7..262953ec7 100644 --- a/FreeAPS/Sources/Services/WatchManager/WatchManager.swift +++ b/FreeAPS/Sources/Services/WatchManager/WatchManager.swift @@ -16,6 +16,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable { @Injected() private var storage: FileStorage! @Injected() private var carbsStorage: CarbsStorage! @Injected() private var tempTargetsStorage: TempTargetsStorage! + @Injected() private var garmin: GarminManager! private var lifetime = Lifetime() @@ -41,6 +42,13 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable { broadcaster.register(PumpBatteryObserver.self, observer: self) broadcaster.register(PumpReservoirObserver.self, observer: self) + garmin.stateRequet = { [weak self] () -> Data in + guard let self = self, let data = try? JSONEncoder().encode(self.state) else { + warning(.service, "Cannot encode watch state") + return Data() + } + return data + } configureState() } @@ -49,10 +57,17 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable { let glucoseValues = self.glucoseText() self.state.glucose = glucoseValues.glucose self.state.trend = glucoseValues.trend + self.state.trendRaw = self.glucoseStorage.recent().last?.direction?.rawValue self.state.delta = glucoseValues.delta self.state.glucoseDate = self.glucoseStorage.recent().last?.dateString + self.state.glucoseDateInterval = self.state.glucoseDate.map { UInt64($0.timeIntervalSince1970) } self.state.lastLoopDate = self.enactedSuggestion?.recieved == true ? self.enactedSuggestion?.deliverAt : self .apsManager.lastLoopDate + + self.state.lastLoopDateInterval = self.state.lastLoopDate.map { + let time = $0.timeIntervalSince1970 >= 0 ? $0.timeIntervalSince1970 : 0 + return UInt64(time) + } self.state.bolusIncrement = self.settingsManager.preferences.bolusIncrement self.state.maxCOB = self.settingsManager.preferences.maxCOB self.state.maxBolus = self.settingsManager.pumpSettings.maxBolus @@ -79,7 +94,9 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable { ) } self.state.bolusAfterCarbs = !self.settingsManager.settings.skipBolusScreenAfterCarbs - self.state.eventualBG = self.evetualBGStraing() + let eBG = self.evetualBGStraing() + self.state.eventualBG = eBG.map { "⇢ " + $0 } + self.state.eventualBGRaw = eBG self.sendState() } @@ -91,6 +108,9 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable { warning(.service, "Cannot encode watch state") return } + + garmin.sendState(data) + guard session.isReachable else { return } session.sendMessageData(data, replyHandler: nil) { error in warning(.service, "Cannot send message to watch", error: error) @@ -145,7 +165,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable { return nil } let units = settingsManager.settings.units - return "⇢ " + eventualFormatter.string( + return eventualFormatter.string( from: (units == .mmolL ? eventualBG.asMmolL : Decimal(eventualBG)) as NSNumber )! } diff --git a/FreeAPSWatch WatchKit Extension/DataFlow.swift b/FreeAPSWatch WatchKit Extension/DataFlow.swift index fc0612a9a..89bc8c539 100644 --- a/FreeAPSWatch WatchKit Extension/DataFlow.swift +++ b/FreeAPSWatch WatchKit Extension/DataFlow.swift @@ -3,9 +3,12 @@ import Foundation struct WatchState: Codable { var glucose: String? var trend: String? + var trendRaw: String? var delta: String? var glucoseDate: Date? + var glucoseDateInterval: UInt64? var lastLoopDate: Date? + var lastLoopDateInterval: UInt64? var bolusIncrement: Decimal? var maxCOB: Decimal? var maxBolus: Decimal? @@ -16,6 +19,7 @@ struct WatchState: Codable { var tempTargets: [TempTargetWatchPreset] = [] var bolusAfterCarbs: Bool? var eventualBG: String? + var eventualBGRaw: String? } struct TempTargetWatchPreset: Codable, Identifiable { diff --git a/FreeAPSWatch WatchKit Extension/Views/ConfirmationView.swift b/FreeAPSWatch WatchKit Extension/Views/ConfirmationView.swift index ab999750b..0b5b53bfa 100644 --- a/FreeAPSWatch WatchKit Extension/Views/ConfirmationView.swift +++ b/FreeAPSWatch WatchKit Extension/Views/ConfirmationView.swift @@ -48,7 +48,7 @@ struct BlinkingView: View { var body: some View { GeometryReader { geometry in - ForEach(0 ..< Int(count)) { index in + ForEach(0 ..< Int(count), id: \.self) { index in item(forIndex: index, in: geometry.size) .frame(width: geometry.size.width, height: geometry.size.height) }