├── xDripClientUI ├── xDripClientUI.xcassets │ ├── Contents.json │ └── xDrip4iOS.imageset │ │ ├── RoundedIcon.png │ │ └── Contents.json ├── Base.lproj │ └── Localizable.strings ├── da.lproj │ └── Localizable.strings ├── de.lproj │ └── Localizable.strings ├── es.lproj │ └── Localizable.strings ├── fi.lproj │ └── Localizable.strings ├── fr.lproj │ └── Localizable.strings ├── it.lproj │ └── Localizable.strings ├── ja.lproj │ └── Localizable.strings ├── nb.lproj │ └── Localizable.strings ├── nl.lproj │ └── Localizable.strings ├── pl.lproj │ └── Localizable.strings ├── ro.lproj │ └── Localizable.strings ├── ru.lproj │ └── Localizable.strings ├── sv.lproj │ └── Localizable.strings ├── tr.lproj │ └── Localizable.strings ├── vi.lproj │ └── Localizable.strings ├── pt-BR.lproj │ └── Localizable.strings ├── zh-Hans.lproj │ └── Localizable.strings ├── xDripClientUI.h ├── Info.plist ├── xDripStatusModel.swift ├── xDripCGMManager+UI.swift ├── MailView.swift ├── UICoordinator.swift └── xDripStatusView.swift ├── Cartfile ├── Cartfile.resolved ├── Common ├── Framework.swift ├── UIImage.swift ├── LocalizedString.swift ├── ConstantsxDripClient.swift ├── HKUnit.swift ├── OSLog.swift └── Trace.swift ├── xDripClient ├── xDripClient.h ├── Info.plist ├── xDripReading.swift ├── xDripAppGroup.swift ├── BluetoothTransmitter.swift └── xDripCGMManager.swift ├── Extensions ├── CBManagerState.swift ├── URL.swift └── StringProtocol.swift ├── xDripClientPlugin ├── xDripClientPlugin.h ├── xDripClientPlugin.swift └── Info.plist ├── LICENSE ├── .gitignore ├── README.md └── xDripClient.xcodeproj └── project.pbxproj /xDripClientUI/xDripClientUI.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /xDripClientUI/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/Base.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/da.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/da.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/de.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/es.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/fi.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/fi.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/fr.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/it.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/ja.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/nb.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/nb.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/nl.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/pl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/pl.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/ro.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/ro.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/sv.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/sv.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/tr.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/vi.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/vi.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/pt-BR.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/pt-BR.lproj/Localizable.strings -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "LoopKit/LoopKit" "loop-release/v2.2.5" 2 | github "LoopKit/MKRingProgressView" "appex-safe" 3 | github "ps2/rileylink_ios" "loop-release/v2.2.5" 4 | -------------------------------------------------------------------------------- /xDripClientUI/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /xDripClientUI/xDripClientUI.xcassets/xDrip4iOS.imageset/RoundedIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohanDegraeve/xdrip-client-swift-1/HEAD/xDripClientUI/xDripClientUI.xcassets/xDrip4iOS.imageset/RoundedIcon.png -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "LoopKit/LoopKit" "3a67f4ac7a1f2484f8527304eb14f21e30f6ca95" 2 | github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" 3 | github "ps2/rileylink_ios" "15d19970f589d1678a486a9b7cfa2430111ee3ea" 4 | -------------------------------------------------------------------------------- /Common/Framework.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Framework.swift 3 | // xDripClient 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | internal class FrameworkBundle { 13 | static let main = Bundle(for: FrameworkBundle.self) 14 | } 15 | -------------------------------------------------------------------------------- /Common/UIImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage.swift 3 | // xDripClientUI 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | extension UIImage { 14 | convenience init?(named name: String) { 15 | self.init(named: name, in: FrameworkBundle.main, compatibleWith: nil) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /xDripClientUI/xDripClientUI.xcassets/xDrip4iOS.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "RoundedIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /xDripClient/xDripClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // xDripClient.h 3 | // xDripClient 4 | // 5 | // Created by Julian Groen on 14/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for xDripClient. 12 | FOUNDATION_EXPORT double xDripClientVersionNumber; 13 | 14 | //! Project version string for xDripClient. 15 | FOUNDATION_EXPORT const unsigned char xDripClientVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /xDripClientUI/xDripClientUI.h: -------------------------------------------------------------------------------- 1 | // 2 | // xDripClientUI.h 3 | // xDripClientUI 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for xDripClientUI. 12 | FOUNDATION_EXPORT double xDripClientUIVersionNumber; 13 | 14 | //! Project version string for xDripClientUI. 15 | FOUNDATION_EXPORT const unsigned char xDripClientUIVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Extensions/CBManagerState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreBluetooth 3 | 4 | extension CBManagerState { 5 | func toString() -> String { 6 | switch self { 7 | case .poweredOff: 8 | return "poweredOff" 9 | case .poweredOn: 10 | return "poweredOn" 11 | case .resetting: 12 | return "resetting" 13 | case .unauthorized: 14 | return "unauthorized" 15 | case .unknown: 16 | return "unknown" 17 | case .unsupported: 18 | return "unsupported" 19 | @unknown default: 20 | return "unknown state" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /xDripClientPlugin/xDripClientPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // xDripClientPlugin.h 3 | // xDripClientPlugin 4 | // 5 | // Created by Julian Groen on 14/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for xDripClientPlugin. 12 | FOUNDATION_EXPORT double xDripClientPluginVersionNumber; 13 | 14 | //! Project version string for xDripClientPlugin. 15 | FOUNDATION_EXPORT const unsigned char xDripClientPluginVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /xDripClientPlugin/xDripClientPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xDripClientPlugin.swift 3 | // xDripClientPlugin 4 | // 5 | // Created by Julian Groen on 14/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LoopKitUI 11 | import xDripClient 12 | import xDripClientUI 13 | import os.log 14 | 15 | 16 | class xDripClientPlugin: NSObject, CGMManagerUIPlugin { 17 | 18 | private let log = OSLog(category: "xDripClientPlugin") 19 | 20 | public var cgmManagerType: CGMManagerUI.Type? { 21 | return xDripCGMManager.self 22 | } 23 | 24 | override init() { 25 | super.init() 26 | log.default("Instantiated xDripClient plugin.") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Extensions/URL.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // source : https://stackoverflow.com/questions/28268145/get-file-size-in-swift 4 | extension URL { 5 | 6 | var attributes: [FileAttributeKey : Any]? { 7 | do { 8 | return try FileManager.default.attributesOfItem(atPath: path) 9 | } catch let error as NSError { 10 | print("FileAttribute error: \(error)") 11 | } 12 | return nil 13 | } 14 | 15 | var fileSize: UInt64 { 16 | return attributes?[.size] as? UInt64 ?? UInt64(0) 17 | } 18 | 19 | var fileSizeString: String { 20 | return ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file) 21 | } 22 | 23 | var creationDate: Date? { 24 | return attributes?[.creationDate] as? Date 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /xDripClient/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /xDripClientUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /xDripClient/xDripReading.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xDripReading.swift 3 | // xDripClient 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LoopKit 11 | import LoopKitUI 12 | import HealthKit 13 | 14 | public struct xDripReading: GlucoseValue, GlucoseDisplayable { 15 | 16 | public var trendType: GlucoseTrend? 17 | 18 | public var trendRate: HKQuantity? 19 | 20 | public var isLocal: Bool = false 21 | 22 | public var glucoseRangeCategory: GlucoseRangeCategory? 23 | 24 | public var quantity: HKQuantity 25 | 26 | public var startDate: Date 27 | 28 | public var isStateValid: Bool { 29 | let glucoseValue = quantity.doubleValue(for: .milligramsPerDeciliter) 30 | return glucoseValue >= 39 && glucoseValue <= 500 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Extensions/StringProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringProtocol.swift 3 | // HelpDiabetes 4 | // 5 | // Created by Johan Degraeve on 08/06/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | //https://stackoverflow.com/questions/32305891/index-of-a-substring-in-a-string-with-swift/32306142 11 | extension StringProtocol where Index == String.Index { 12 | ///can be used to split a string in array of strings, splitted by other string 13 | func indexes(of string: Self, options: String.CompareOptions = []) -> [Index] { 14 | var result: [Index] = [] 15 | var start = startIndex 16 | while start < endIndex, 17 | let range = self[start.. String { 14 | if let value = value { 15 | return NSLocalizedString(key, tableName: tableName, bundle: FrameworkBundle.main, value: value, comment: comment) 16 | } else { 17 | return NSLocalizedString(key, tableName: tableName, bundle: FrameworkBundle.main, comment: comment) 18 | } 19 | } 20 | 21 | extension DefaultStringInterpolation { 22 | mutating func appendInterpolation(_ optional: T?) { 23 | appendInterpolation(String(describing: optional)) 24 | } 25 | } 26 | 27 | extension Text { 28 | init(_ key: String, comment: String) { 29 | self.init(LocalizedString(key, comment: comment)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /xDripClientPlugin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | com.loopkit.Loop.CGMManagerDisplayName 22 | xDrip4iOS 23 | com.loopkit.Loop.CGMManagerIdentifier 24 | xDripClient 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Julian Groen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Common/ConstantsxDripClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // xDripClient 4 | // 5 | // Created by Johan Degraeve on 14/05/2022. 6 | // Copyright © 2022 Randall Knutson. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ConstantsxDripClient { 12 | 13 | /// for use in NSLog 14 | static let tracePrefix = "loop-NSLog" 15 | 16 | /// email address to which to send trace file 17 | static let traceFileDestinationAddress = "xdrip@proximus.be" 18 | 19 | /// - will be used as filename to store traces on disk, and attachment file name when sending trace via e-mail 20 | /// - filename will be extended with digit, eg looptrace.0.log, or looptrace.1.log - the latest trace file is always looptrace.0.log 21 | static let traceFileName = "looptrace" 22 | 23 | /// maximum size of one trace file, in MB. If size is larger, files will rotate, ie all trace files will be renamed, from looptrace.2.log to looptrace.3.log, from looptrace.1.log to looptrace.2.log, from looptrace.0.log to looptrace.1.log, 24 | static let maximumFileSizeInMB: UInt64 = 3 25 | 26 | /// maximum amount of trace files to hold. When rotating, and if value is 3, then tracefile looptrace.2.log will be deleted 27 | static let maximumAmountOfTraceFiles = 3 28 | 29 | /// used only in logging 30 | static let osLogSubSystemName = "xDripClientSwift" 31 | 32 | /// category used in debug logging 33 | static let debuglogging = "loopdebuglogging" 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /xDripClientUI/xDripStatusModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xDripStatusModel.swift 3 | // xDripClientUI 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import xDripClient 10 | import LoopKit 11 | import LoopKitUI 12 | import HealthKit 13 | 14 | 15 | class xDripStatusModel: NSObject, ObservableObject { 16 | 17 | let cgmManager: xDripCGMManager 18 | let displayGlucosePreference: DisplayGlucosePreference 19 | var hasCompleted: (() -> Void)? 20 | 21 | var preferredUnit: HKUnit { 22 | return displayGlucosePreference.unit 23 | } 24 | 25 | var latestReading: xDripReading? { 26 | return cgmManager.latestReading 27 | } 28 | 29 | lazy var unitFormatter: QuantityFormatter = { 30 | let formatter = QuantityFormatter(for: preferredUnit) 31 | return formatter 32 | }() 33 | 34 | lazy var dateFormatter: DateFormatter = { 35 | let formatter = DateFormatter() 36 | formatter.dateStyle = .long 37 | formatter.timeStyle = .long 38 | formatter.doesRelativeDateFormatting = true 39 | return formatter 40 | }() 41 | 42 | init(cgmManager: xDripCGMManager, for displayGlucosePreference: DisplayGlucosePreference) { 43 | self.cgmManager = cgmManager 44 | self.displayGlucosePreference = displayGlucosePreference 45 | } 46 | 47 | func notifyDeletion() { 48 | cgmManager.notifyDelegateOfDeletion { 49 | DispatchQueue.main.async { self.hasCompleted?() } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Common/HKUnit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HKUnit.swift 3 | // xDripClient 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import HealthKit 10 | 11 | 12 | extension HKUnit { 13 | 14 | static let milligramsPerDeciliter: HKUnit = { 15 | return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) 16 | }() 17 | 18 | static let millimolesPerLiter: HKUnit = { 19 | return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) 20 | }() 21 | 22 | static let internationalUnitsPerHour: HKUnit = { 23 | return HKUnit.internationalUnit().unitDivided(by: .hour()) 24 | }() 25 | 26 | static let gramsPerUnit: HKUnit = { 27 | return HKUnit.gram().unitDivided(by: .internationalUnit()) 28 | }() 29 | 30 | var foundationUnit: Unit? { 31 | if self == HKUnit.milligramsPerDeciliter { 32 | return UnitConcentrationMass.milligramsPerDeciliter 33 | } 34 | 35 | if self == HKUnit.millimolesPerLiter { 36 | return UnitConcentrationMass.millimolesPerLiter(withGramsPerMole: HKUnitMolarMassBloodGlucose) 37 | } 38 | 39 | if self == HKUnit.gram() { 40 | return UnitMass.grams 41 | } 42 | 43 | return nil 44 | } 45 | 46 | /// The smallest value expected to be visible on a chart 47 | var chartableIncrement: Double { 48 | if self == .milligramsPerDeciliter { 49 | return 1 50 | } else { 51 | return 1 / 25 52 | } 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Common/OSLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OSLog.swift 3 | // xDripClient 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import os.log 10 | 11 | 12 | extension OSLog { 13 | 14 | convenience init(category: String) { 15 | self.init(subsystem: "com.loopkit.xDripClientPlugin", category: category) 16 | } 17 | 18 | func debug(_ message: StaticString, _ args: CVarArg...) { 19 | log(message, type: .debug, args) 20 | } 21 | 22 | func info(_ message: StaticString, _ args: CVarArg...) { 23 | log(message, type: .info, args) 24 | } 25 | 26 | func `default`(_ message: StaticString, _ args: CVarArg...) { 27 | log(message, type: .default, args) 28 | } 29 | 30 | func error(_ message: StaticString, _ args: CVarArg...) { 31 | log(message, type: .error, args) 32 | } 33 | 34 | private func log(_ message: StaticString, type: OSLogType, _ args: [CVarArg]) { 35 | switch args.count { 36 | case 0: 37 | os_log(message, log: self, type: type) 38 | case 1: 39 | os_log(message, log: self, type: type, args[0]) 40 | case 2: 41 | os_log(message, log: self, type: type, args[0], args[1]) 42 | case 3: 43 | os_log(message, log: self, type: type, args[0], args[1], args[2]) 44 | case 4: 45 | os_log(message, log: self, type: type, args[0], args[1], args[2], args[3]) 46 | case 5: 47 | os_log(message, log: self, type: type, args[0], args[1], args[2], args[3], args[4]) 48 | default: 49 | os_log(message, log: self, type: type, args) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /xDripClientUI/xDripCGMManager+UI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xDripCGMManager+UI.swift 3 | // xDripClientUI 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import HealthKit 11 | import LoopKit 12 | import LoopKitUI 13 | import xDripClient 14 | 15 | 16 | extension xDripCGMManager: CGMManagerUI { 17 | 18 | public static var onboardingImage: UIImage? { 19 | return UIImage(named: "xDrip4iOS") 20 | } 21 | 22 | public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool, prefersToSkipUserInteraction: Bool) -> SetupUIResult { 23 | return .userInteractionRequired(UICoordinator(cgmManager: xDripCGMManager(), displayGlucosePreference: displayGlucosePreference, colorPalette: colorPalette)) 24 | } 25 | 26 | public var smallImage: UIImage? { 27 | return UIImage(named: "xDrip4iOS") 28 | } 29 | 30 | public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> CGMManagerViewController { 31 | return UICoordinator(cgmManager: self, displayGlucosePreference: displayGlucosePreference, colorPalette: colorPalette) 32 | } 33 | 34 | public var cgmStatusHighlight: DeviceStatusHighlight? { 35 | return nil 36 | } 37 | 38 | public var cgmLifecycleProgress: DeviceLifecycleProgress? { 39 | return nil 40 | } 41 | 42 | public var cgmStatusBadge: DeviceStatusBadge? { 43 | return nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /xDripClientUI/MailView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MessageUI 3 | 4 | // https://stackoverflow.com/questions/56784722/swiftui-send-email 5 | public struct MailView: UIViewControllerRepresentable { 6 | 7 | @Environment(\.presentationMode) var presentation 8 | @Binding var result: Result? 9 | public var configure: ((MFMailComposeViewController) -> Void)? 10 | 11 | public class Coordinator: NSObject, MFMailComposeViewControllerDelegate { 12 | 13 | @Binding var presentation: PresentationMode 14 | @Binding var result: Result? 15 | 16 | init(presentation: Binding, 17 | result: Binding?>) { 18 | _presentation = presentation 19 | _result = result 20 | } 21 | 22 | public func mailComposeController(_ controller: MFMailComposeViewController, 23 | didFinishWith result: MFMailComposeResult, 24 | error: Error?) { 25 | defer { 26 | $presentation.wrappedValue.dismiss() 27 | } 28 | guard error == nil else { 29 | self.result = .failure(error!) 30 | return 31 | } 32 | self.result = .success(result) 33 | } 34 | } 35 | 36 | public func makeCoordinator() -> Coordinator { 37 | return Coordinator(presentation: presentation, 38 | result: $result) 39 | } 40 | 41 | public func makeUIViewController(context: UIViewControllerRepresentableContext) -> MFMailComposeViewController { 42 | let vc = MFMailComposeViewController() 43 | vc.mailComposeDelegate = context.coordinator 44 | configure?(vc) 45 | return vc 46 | } 47 | 48 | public func updateUIViewController( 49 | _ uiViewController: MFMailComposeViewController, 50 | context: UIViewControllerRepresentableContext) { 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | .DS_Store 5 | 6 | ## User settings 7 | xcuserdata/ 8 | 9 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 10 | *.xcscmblueprint 11 | *.xccheckout 12 | 13 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 14 | build/ 15 | DerivedData/ 16 | *.moved-aside 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | 29 | ## App packaging 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | # Package.resolved 44 | # *.xcodeproj 45 | # 46 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 47 | # hence it is not needed unless you have added a package configuration file to your project 48 | # .swiftpm 49 | 50 | .build/ 51 | 52 | # CocoaPods 53 | # 54 | # We recommend against adding the Pods directory to your .gitignore. However 55 | # you should judge for yourself, the pros and cons are mentioned at: 56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 57 | # 58 | # Pods/ 59 | # 60 | # Add this line if you want to avoid checking in source code from the Xcode workspace 61 | # *.xcworkspace 62 | 63 | # Carthage 64 | # 65 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 66 | # Carthage/Checkouts 67 | 68 | Carthage/Build/ 69 | 70 | # Accio dependency management 71 | Dependencies/ 72 | .accio/ 73 | 74 | # fastlane 75 | # 76 | # It is recommended to not store the screenshots in the git repo. 77 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 78 | # For more information about the recommended setup visit: 79 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 80 | 81 | fastlane/report.xml 82 | fastlane/Preview.html 83 | fastlane/screenshots/**/*.png 84 | fastlane/test_output 85 | 86 | # Code Injection 87 | # 88 | # After new code Injection tools there's a generated folder /iOSInjectionProject 89 | # https://github.com/johnno1962/injectionforxcode 90 | 91 | iOSInjectionProject/ 92 | 93 | .DS_Store 94 | /.build 95 | /Packages 96 | /*.xcodeproj 97 | xcuserdata/ 98 | DerivedData/ 99 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 100 | 101 | commit.txt 102 | -------------------------------------------------------------------------------- /xDripClientUI/UICoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICoordinator.swift 3 | // xDripClientUI 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import LoopKit 11 | import LoopKitUI 12 | import xDripClient 13 | 14 | 15 | class UICoordinator: UINavigationController, CGMManagerOnboarding, CompletionNotifying, UINavigationControllerDelegate { 16 | 17 | let cgmManager: xDripCGMManager? 18 | let displayGlucosePreference: DisplayGlucosePreference? 19 | let colorPalette: LoopUIColorPalette 20 | 21 | weak var cgmManagerOnboardingDelegate: CGMManagerOnboardingDelegate? 22 | weak var completionDelegate: CompletionDelegate? 23 | 24 | init( 25 | cgmManager: xDripCGMManager? = nil, 26 | displayGlucosePreference: DisplayGlucosePreference? = nil, 27 | colorPalette: LoopUIColorPalette 28 | ) { 29 | self.colorPalette = colorPalette 30 | self.displayGlucosePreference = displayGlucosePreference 31 | self.cgmManager = cgmManager 32 | 33 | super.init(navigationBarClass: UINavigationBar.self, toolbarClass: UIToolbar.self) 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | navigationBar.prefersLargeTitles = true 43 | delegate = self 44 | 45 | if let cgmManager = cgmManager { 46 | setupCompletion(cgmManager) 47 | } 48 | 49 | } 50 | 51 | override func viewWillAppear(_ animated: Bool) { 52 | let controller = viewController(willShow: .status) 53 | setViewControllers([controller], animated: false) 54 | } 55 | 56 | private enum ControllerType: Int, CaseIterable { 57 | case status 58 | } 59 | 60 | private func viewController(willShow view: ControllerType) -> UIViewController { 61 | switch view { 62 | case .status: 63 | guard let cgmManager = cgmManager, let displayGlucosePreference = displayGlucosePreference else { 64 | fatalError() 65 | } 66 | let model = xDripStatusModel(cgmManager: cgmManager, for: displayGlucosePreference) 67 | model.hasCompleted = { [weak self] in 68 | self?.notifyCompletion() 69 | } 70 | let view = xDripStatusView(viewModel: model) 71 | 72 | return viewController(rootView: view) 73 | } 74 | } 75 | 76 | private func viewController(rootView: Content) -> DismissibleHostingController { 77 | return DismissibleHostingController(content: rootView, colorPalette: colorPalette) 78 | } 79 | 80 | private func setupCompletion(_ cgmManager: xDripCGMManager) { 81 | cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didCreateCGMManager: cgmManager) 82 | cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didOnboardCGMManager: cgmManager) 83 | completionDelegate?.completionNotifyingDidComplete(self) 84 | } 85 | 86 | private func notifyCompletion() { 87 | completionDelegate?.completionNotifyingDidComplete(self) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a https://github.com/loopkit/loop plugin to connect https://github.com/JohanDegraeve/xdripswift to Loop 2 | 3 | ## functionality 4 | 5 | - uses xDrip4iOS as a CGM : readings are fetched from UserDefaults, where they are stored by xDrip4iOS. 6 | - can use the CGM as heartbeat (optional) : if enabled, then it will make a connection to the CGM (in parallel to xDrip4iOS) just for the sake of keeping Loop alive. Can be used with Libre or Dexcom. In case of Libre, the reading will run 1 minute behind. 7 | - If you haven't setup your CGM yet in xDrip4iOS : 8 | - Force close Loop 9 | - in xDrip4iOS make sure you have made a first connection to the CGM 10 | - force close xDrip4iOS 11 | - reopen Loop 12 | - select xDrip4iOS as CGM and open the xDrip4iOS UI 13 | - enable "use CGM as heartbeat" 14 | - keep the app in the foreground and wait till the text under the UISwitch changes to "Did connect to CGM. You can now run both xDrip4iOS and Loop". Once you see this text, you can reopen xDrip4iOS 15 | - option to enable/disalbe "Loop should sync to remote service" : in case you let xDrip4iOS upload readings to NightScout, then you can disable this, otherwise all readings will be uploaded twice 16 | - There's also an option in the xDrip4iOS UI to lock the screen, ie the keep Loop in the foreground 17 | - Option to send issue report : this will send logging informatin related to the heartbeat mechanism only. It's send by default to xdrip@proximus.be 18 | 19 | Latest test done with loop dev branch, commit 6286f61a61a9794179f551f076c3b2b0ec127dac 20 | 21 | Based on client written by Julian Groen : https://github.com/julian-groen/xdrip-client-swift 22 | 23 | ## Prerequisites 24 | 25 | - This only works if both xDrip4iOS and Loop are built using the same App Group! 26 | 27 | ## Start with a clean LoopWorkspace based on dev 28 | 29 | * Download a fresh copy of LoopWorkspace dev into a subfolder of your choice 30 | ``` 31 | cd to your preferred directory 32 | git clone --branch=dev --recurse-submodules https://github.com/LoopKit/LoopWorkspace 33 | cd LoopWorkspace 34 | ``` 35 | 36 | * Add xDripClient submodule 37 | ``` 38 | git submodule add -b master https://github.com/johandegraeve/xdrip-client-swift-1.git xdrip-client-swift 39 | ``` 40 | 41 | ## In Xcode, add xDripClient project into LoopWorkspace 42 | 1. Open Loop.xcworkspace 43 | 2. Drag xDripClient.xcodeproj from the Finder (from the xDripClient submodule) into the xcode left menu while having the loop workspace open 44 | 3. Select the "Loop (Workspace)" scheme and then "Edit scheme.." 45 | 4. In the Build Dialog, make sure to add xDripClientPlugin as a build target, and place it just before "ShareClientPlugin" 46 | 5 In Xcode 13 this can be accessed from the top menu `Product -> Scheme -> Edit Scheme` 47 | 48 |
49 | Reference material: Step 2 50 | 51 | ![Schermafbeelding 2022-03-15 om 20 42 54](https://user-images.githubusercontent.com/55219001/158459048-e0fd4d82-780c-4452-851d-4d48a3e15594.png) 52 | 53 |
54 | 55 |
56 | Reference material: Step 4 57 | 58 | ![Schermafbeelding 2022-03-15 om 20 43 16](https://user-images.githubusercontent.com/55219001/158459062-1e267e3f-33cb-431b-874c-688555a7a099.png) 59 | 60 |
61 | 62 | ## Build the LoopWorkspace with xDripClient Plugin 63 | * In xcode, go to Product->"Clean Build Folder" 64 | * Make sure you build "Loop (Workspace)" rather than "Loop" 65 | 66 | ## Troubleshooting 67 | Loop not getting xDrip data? 68 | * Wait a moment. On first launch the plugin will probably be empty, as it doesn't prompt xDrip to re-read. 69 | * Make sure xDrip is receiving readings! (Did you open the correct copy of xDrip? You might now have two copies. In case you have, make sure only one copy receives readings from the transmitter.) 70 | * Make sure Loop and xDrip are using the same App Group! Really! 71 | * Make sure both Loop and xDrip are still running in the background - try killing recent apps, then killing and reopening Loop and xDrip. Make ure they have all the iOS permissions they need. 72 | 73 | ## Miscellaneous (Navigate to xDrip4iOS via Loop HUD) 74 | 75 |
76 | Add 'xdripswift' to LSApplicationQueriesSchemes 77 | 78 | ![Schermafbeelding 2022-03-15 om 20 38 37](https://user-images.githubusercontent.com/55219001/158460127-6f55a457-fcb4-4dbd-ba55-8c744b66782a.png) 79 | 80 |
81 | -------------------------------------------------------------------------------- /xDripClient/xDripAppGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xDripAppGroup.swift 3 | // xDripClient 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Randall Knutson. All rights reserved. 7 | // 8 | 9 | import LoopKit 10 | import HealthKit 11 | import Combine 12 | 13 | 14 | public class xDripAppGroup { 15 | 16 | public enum AppGroupError: Error { 17 | case data(reason: String) 18 | } 19 | 20 | public let sharedUserDefaults: UserDefaults? 21 | 22 | /// key for shared userdefaults 23 | public static let keyForcgmTransmitterDeviceAddress = "cgmTransmitterDeviceAddress" 24 | 25 | /// the mac address of the cgm to which xDrip4iOS is connecting. Nil if none defined 26 | /// - set by xdrip4ios. xDripClient will need to read it regularly to check if it has changed 27 | public var cgmTransmitterDeviceAddress: String? { 28 | 29 | if let cGMTransmitterAddress = sharedUserDefaults?.string(forKey: xDripAppGroup.keyForcgmTransmitterDeviceAddress) { 30 | return cGMTransmitterAddress 31 | } else { 32 | return nil 33 | } 34 | 35 | } 36 | 37 | /// the service uuid to discover, see description cgmTransmitterDeviceAddress 38 | public var cgmTransmitter_CBUUID_Service: String? { 39 | 40 | if let cgmTransmitter_CBUUID_Service = sharedUserDefaults?.string(forKey: "cgmTransmitter_CBUUID_Service") { 41 | return cgmTransmitter_CBUUID_Service 42 | } else { 43 | return nil 44 | } 45 | 46 | } 47 | 48 | /// the receive characteristic to subscribe too, see description cgmTransmitterDeviceAddress 49 | public var cgmTransmitter_CBUUID_Receive: String? { 50 | 51 | if let cgmTransmitter_CBUUID_Receive = sharedUserDefaults?.string(forKey: "cgmTransmitter_CBUUID_Receive") { 52 | return cgmTransmitter_CBUUID_Receive 53 | } else { 54 | return nil 55 | } 56 | 57 | } 58 | 59 | public init(_ group: String? = Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String) { 60 | sharedUserDefaults = UserDefaults.init(suiteName: group) 61 | } 62 | 63 | public func fetchLatestReadings() throws -> Array { 64 | guard let encodedLatestReadings = sharedUserDefaults?.data(forKey: "latestReadings") else { 65 | throw AppGroupError.data(reason: "Couldn't fetch latest readings from xDrip4iOS.") 66 | } 67 | 68 | let decodedLatestReadings = try? JSONSerialization.jsonObject(with: encodedLatestReadings, options: []) 69 | guard let latestReadings = decodedLatestReadings as? Array else { 70 | throw AppGroupError.data(reason: "Couldn't decode latest readings from xDrip4iOS.") 71 | } 72 | 73 | var transformedReadings: Array = [] 74 | for reading in latestReadings { 75 | 76 | // to check that the source of the reading is xDrip4iOS 77 | guard let from = reading["from"] as? String, from == "xDrip" else { 78 | continue 79 | } 80 | 81 | var glucoseTrendType: GlucoseTrend? 82 | if let rawGlucoseTrendType = reading["Trend"] as? Int { 83 | glucoseTrendType = GlucoseTrend(rawValue: rawGlucoseTrendType) 84 | } 85 | 86 | var glucoseValue: HKQuantity? 87 | if let rawGlucoseValue = reading["Value"] as? Double { 88 | glucoseValue = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: rawGlucoseValue) 89 | } 90 | 91 | var glucoseStartDate: Date? 92 | if let rawGlucoseStartDate = reading["DT"] as? String { 93 | glucoseStartDate = try self.parseTimestamp(rawGlucoseStartDate) 94 | } 95 | 96 | if let trend = glucoseTrendType, let glucose = glucoseValue, let datetime = glucoseStartDate { 97 | let reading = xDripReading(trendType: trend, quantity: glucose, startDate: datetime) 98 | transformedReadings.append(reading) 99 | } 100 | } 101 | return transformedReadings 102 | } 103 | 104 | private func parseTimestamp(_ timestamp: String) throws -> Date? { 105 | let regex = try NSRegularExpression(pattern: "\\((.*)\\)") 106 | if let match = regex.firstMatch(in: timestamp, range: NSMakeRange(0, timestamp.count)) { 107 | let epoch = Double((timestamp as NSString).substring(with: match.range(at: 1)))! / 1000 108 | return Date(timeIntervalSince1970: epoch) 109 | } 110 | return nil 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /xDripClientUI/xDripStatusView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xDripStatusView.swift 3 | // xDripClientUI 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | // Adapted by Johan Degraeve 9 | 10 | import SwiftUI 11 | import LoopKit 12 | import LoopKitUI 13 | import HealthKit 14 | import xDripClient 15 | import CoreBluetooth 16 | import MessageUI 17 | 18 | struct xDripStatusView: View where Model: xDripStatusModel { 19 | 20 | @ObservedObject var viewModel: Model 21 | 22 | @Environment(\.glucoseTintColor) var glucoseTintColor 23 | @Environment(\.guidanceColors) var guidanceColors 24 | 25 | @State var showingDeleteConfirmation = false 26 | 27 | @State private var showEmailNotConfiguredWarning = false 28 | @State private var result: Result? = nil 29 | @State private var isShowingMailView = false 30 | 31 | @State private var showScreenLockConfirmation = false 32 | 33 | @AppStorage(UserDefaults.Key.useCGMAsHeartbeat.rawValue) private var useCGMAsHeartbeat: Bool = false 34 | 35 | @AppStorage(UserDefaults.Key.heartBeatState.rawValue) private var heartBeatState: String = "" 36 | 37 | @AppStorage(UserDefaults.Key.shouldSyncToRemoteService.rawValue) private var shouldSyncToRemoteService: Bool = false 38 | 39 | /// for some reason the TextEditor that shows the heartBeatState doesn't immediately use multiline. By removing and re-adding it, multiline is used. A trick to force multiline, is to set showHeartBeatText to false as soon as the View is shown, and immediately back to true. Then multiline is used 40 | @State var showHeartBeatText = true 41 | 42 | var body: some View { 43 | List { 44 | overviewSection 45 | heartBeatSection 46 | shouldSyncToRemoteServiceSection 47 | lockScreenSection 48 | deletionSection 49 | } 50 | .insetGroupedListStyle() 51 | .navigationBarTitle(Text("xDrip4iOS", comment: "Title text for the CGM status view")) 52 | .navigationBarItems(trailing: dismissButton) 53 | } 54 | 55 | var lockScreenSection: some View { 56 | 57 | VStack(alignment: .leading) { 58 | Text("Lock Screen", comment: "The title text for the cell to lock the screen") 59 | .padding(.vertical, 3) 60 | } 61 | .onTapGesture { 62 | // prevent screen dim/lock 63 | UIApplication.shared.isIdleTimerDisabled = true 64 | 65 | UserDefaults.standard.screenLockedByxDrip4iOSClient = true 66 | 67 | showScreenLockConfirmation = true 68 | 69 | } 70 | .alert(isPresented: $showScreenLockConfirmation, content: { Alert(title: Text("Screen locked. Bring the app to the background to unlock.", comment: "confirmation that screen is locked")) }) 71 | 72 | 73 | } 74 | 75 | var overviewSection: some View { 76 | Section() { 77 | VStack(alignment: .leading) { 78 | Spacer() 79 | HStack(alignment: .center) { 80 | Image(uiImage: UIImage(named: "xDrip4iOS") ?? UIImage()) 81 | .resizable() 82 | .aspectRatio(contentMode: ContentMode.fit) 83 | .frame(height: 85) 84 | DescriptiveText(label: "Click on 'Open App' down below to quickly navigate to xDrip4iOS for any adjustment to settings etc.") 85 | } 86 | Spacer() 87 | } 88 | Link("Open App", destination: viewModel.cgmManager.appURL!) 89 | .foregroundColor(.blue) 90 | } 91 | .contentShape(Rectangle()) 92 | } 93 | 94 | var heartBeatSection: some View { 95 | 96 | Section(header: SectionHeader(label: LocalizedString("Heartbeat", comment: "Section title for heartbeat info"))) { 97 | 98 | Toggle(isOn: $useCGMAsHeartbeat) { 99 | VStack(alignment: .leading) { 100 | Text("Use CGM as heartbeat", comment: "The title text for the cgm heartbeat enabled switch cell") 101 | .padding(.vertical, 3) 102 | } 103 | } 104 | if useCGMAsHeartbeat && showHeartBeatText { 105 | VStack(alignment: .leading) { 106 | TextEditor(text: $heartBeatState) 107 | .multilineTextAlignment(.leading) 108 | .disabled(true) 109 | .lineLimit(nil) 110 | 111 | } 112 | 113 | } 114 | 115 | } 116 | } 117 | 118 | var shouldSyncToRemoteServiceSection: some View { 119 | 120 | Section(header: SectionHeader(label: LocalizedString("Sync", comment: "Section title for sync to remote service section"))) { 121 | 122 | Toggle(isOn: $shouldSyncToRemoteService) { 123 | VStack(alignment: .leading) { 124 | Text("Loop should sync to remote service", comment: "The title text for sync to remote service enabled switch cell") 125 | .padding(.vertical, 3) 126 | } 127 | } 128 | 129 | } 130 | // for some reason the heartbeat text in heartBeatSection is not completely shown, unless it's refreshed done at the right moment, which is here. 131 | .onAppear(perform: { 132 | showHeartBeatText = false; 133 | showHeartBeatText = true 134 | }) 135 | 136 | } 137 | 138 | var sendTraceFileSection: some View { 139 | Section { 140 | Button(action: { 141 | if MFMailComposeViewController.canSendMail() { 142 | self.isShowingMailView.toggle() 143 | } else { 144 | print("Can't send emails from this device") 145 | showEmailNotConfiguredWarning = true 146 | } 147 | if result != nil { 148 | print("Result: \(String(describing: result))") 149 | } 150 | }) { 151 | HStack { 152 | Image(systemName: "envelope") 153 | Text("Send Issue Report", comment: "Title text for the button to send issue report") 154 | 155 | } 156 | } 157 | } 158 | .sheet(isPresented: $isShowingMailView) { 159 | MailView(result: $result) { composer in 160 | 161 | composer.setToRecipients(["xdrip@proximus.be"]) 162 | composer.setMessageBody(NSLocalizedString("Problem Description: ", comment: "default text in email body, when user wants to send trace file."), isHTML: true) 163 | 164 | // add all trace files as attachment 165 | let traceFilesInData = Trace.getTraceFilesInData() 166 | for (index, traceFileInData) in traceFilesInData.0.enumerated() { 167 | composer.addAttachmentData(traceFileInData as Data, mimeType: "text/txt", fileName: traceFilesInData.1[index]) 168 | } 169 | 170 | } 171 | } 172 | .alert(isPresented: $showEmailNotConfiguredWarning, content: { Alert(title: Text("You must have an email account configured.", comment: "Explain to user that email account must be configured")) }) 173 | } 174 | 175 | var deletionSection: some View { 176 | Section(header: Spacer()) { 177 | Button(action: { 178 | showingDeleteConfirmation = true 179 | }, label: { 180 | HStack { 181 | Spacer() 182 | Text("Delete CGM", comment: "Title text for the button to remove a CGM from Loop") 183 | .foregroundColor(guidanceColors.critical) 184 | Spacer() 185 | } 186 | }).actionSheet(isPresented: $showingDeleteConfirmation) { 187 | deleteConfirmationActionSheet 188 | } 189 | } 190 | } 191 | 192 | var deleteConfirmationActionSheet: ActionSheet { 193 | ActionSheet(title: Text("Are you sure you want to delete this CGM?", comment: "Confirmation message for deleting a CGM"), buttons: [ 194 | .destructive(Text("Delete CGM", comment: "Title text for the button to remove a CGM from Loop")) { viewModel.notifyDeletion() }, 195 | .cancel() 196 | ]) 197 | } 198 | 199 | var dismissButton: some View { 200 | Button(action: { viewModel.hasCompleted?() }) { Text("Done").bold() } 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /Common/Trace.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import os 3 | 4 | /// log only used for debuglogging 5 | fileprivate var log:OSLog = { 6 | let log:OSLog = OSLog(subsystem: ConstantsxDripClient.osLogSubSystemName, category: ConstantsxDripClient.debuglogging) 7 | return log 8 | }() 9 | 10 | /// dateformatter for nslog 11 | fileprivate let dateFormatNSLog: DateFormatter = { 12 | 13 | let dateFormatter = DateFormatter() 14 | 15 | dateFormatter.dateFormat = "y-MM-dd HH:mm:ss.SSSS" 16 | 17 | return dateFormatter 18 | 19 | }() 20 | 21 | /// used during development 22 | func debuglogging(_ logtext:String) { 23 | os_log("%{public}@", log: log, type: .debug, logtext) 24 | } 25 | 26 | /// finds the path to where loop can save files 27 | fileprivate func getDocumentsDirectory() -> URL { 28 | let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 29 | return paths[0] 30 | } 31 | 32 | /// filename for tracing. (UI would not be be developed to send the file via e-mail 33 | fileprivate var traceFileName:URL? 34 | 35 | 36 | /// function to be used for logging, takes same parameters as os_log but in a next phase also NSLog can be added, or writing to disk to send later via e-mail .. 37 | /// - message : the text, same format as in os_log with %{private} and %{public} to either keep variables private or public , for NSLog, only 3 String formatters are suppored "@" for String, "d" for Int, "f" for double. 38 | /// - category is the same as used for creating the log (see class ConstantsLog), it's repeated here to use in NSLog 39 | /// - args : optional list of parameters that will be used. MAXIMUM 10 ! 40 | /// 41 | /// Example 42 | func trace(_ message: StaticString, category: String, _ args: CVarArg...) { 43 | 44 | // initialize traceFileName if needed 45 | if traceFileName == nil { 46 | traceFileName = getDocumentsDirectory().appendingPathComponent(ConstantsxDripClient.traceFileName + ".0.log") 47 | } 48 | guard let traceFileName = traceFileName else {return} 49 | 50 | var argumentsCounter: Int = 0 51 | 52 | var actualMessage = message.description 53 | 54 | // try to find the publicMark as long as argumentsCounter is less than the number of arguments 55 | while argumentsCounter < args.count { 56 | 57 | // mark to replace 58 | let publicMark = "%{public}" 59 | 60 | // get array of indexes of location of publicMark 61 | let indexesOfPublicMark = actualMessage.indexes(of: "%{public}") 62 | 63 | if indexesOfPublicMark.count > 0 { 64 | 65 | // range starts from first character until just before the publicMark 66 | let startOfMessageRange = actualMessage.startIndex.. 3 * 1024 * 1024 { 144 | 145 | rotateTraceFiles() 146 | 147 | } 148 | 149 | } 150 | 151 | fileprivate func rotateTraceFiles() { 152 | 153 | // assign fileManager 154 | let fileManager = FileManager.default 155 | 156 | // first check if last trace file exists 157 | let lastFile = getDocumentsDirectory().appendingPathComponent(ConstantsxDripClient.traceFileName + ".0.log") 158 | 159 | if FileHandle(forWritingAtPath: lastFile.path) != nil { 160 | 161 | do { 162 | try fileManager.removeItem(at: lastFile) 163 | } catch { 164 | debuglogging("failed to delete file " + lastFile.absoluteString) 165 | } 166 | 167 | } 168 | 169 | // now rename trace files if they exist, 170 | for indexFrom0ToMax in 0...1 { 171 | 172 | let index = 1 - indexFrom0ToMax 173 | 174 | let file = getDocumentsDirectory().appendingPathComponent(ConstantsxDripClient.traceFileName + "." + index.description + ".log") 175 | let newFile = getDocumentsDirectory().appendingPathComponent(ConstantsxDripClient.traceFileName + "." + (index + 1).description + ".log") 176 | 177 | if FileHandle(forWritingAtPath: file.path) != nil { 178 | 179 | do { 180 | try fileManager.moveItem(at: file, to: newFile) 181 | } catch { 182 | debuglogging("failed to rename file " + lastFile.absoluteString) 183 | } 184 | 185 | } 186 | } 187 | 188 | // now set tracefilename to nil, it will be reassigned to correct name, ie the one with index 0, at next usage 189 | traceFileName = nil 190 | 191 | } 192 | 193 | public class Trace { 194 | 195 | public init() { 196 | 197 | } 198 | 199 | /// returns tuple, first type is an array of Data, each element is a tracefile converted to Data, second type is String, each element is the name of the tracefile 200 | public static func getTraceFilesInData() -> ([Data], [String]) { 201 | 202 | var traceFilesInData = [Data]() 203 | var traceFileNames = [String]() 204 | 205 | for index in 0..<3 { 206 | 207 | let filename = ConstantsxDripClient.traceFileName + "." + index.description + ".log" 208 | 209 | let file = getDocumentsDirectory().appendingPathComponent(filename) 210 | 211 | if FileHandle(forWritingAtPath: file.path) != nil { 212 | 213 | do { 214 | // create traceFile info as data 215 | let fileData = try Data(contentsOf: file) 216 | traceFilesInData.append(fileData) 217 | traceFileNames.append(filename) 218 | } catch { 219 | debuglogging("failed to create data from " + filename) 220 | } 221 | 222 | } 223 | } 224 | 225 | return (traceFilesInData, traceFileNames) 226 | 227 | } 228 | 229 | /// to call Trace function from other modules. Code in the xDripClient module can directly call trace without using the Class Trace 230 | public func callTrace(_ message: StaticString, category: String, _ args: CVarArg...) { 231 | trace(message, category: category, args) 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /xDripClient/BluetoothTransmitter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreBluetooth 3 | import os 4 | 5 | /// Generic bluetoothtransmitter class that handles scanning, connect, discover services, discover characteristics, subscribe to receive characteristic, reconnect. 6 | /// 7 | /// - the connection will be set up and a subscribe to a characteristic will be done 8 | /// - a heartbeat function is called each time there's a disconnect (needed for Dexcom) or if there's data received on the receive characteristic 9 | /// - the class does nothing with the data itself 10 | class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate { 11 | 12 | // MARK: - private properties 13 | 14 | /// the address of the transmitter. 15 | private let deviceAddress:String 16 | 17 | /// services to be discovered 18 | private let servicesCBUUIDs:[CBUUID] 19 | 20 | /// receive characteristic to which we should subcribe in order to awake the app when the tarnsmitter sends data 21 | private let CBUUID_ReceiveCharacteristic:String 22 | 23 | /// centralManager 24 | private var centralManager: CBCentralManager? 25 | 26 | /// the receive Characteristic 27 | private var receiveCharacteristic:CBCharacteristic? 28 | 29 | /// peripheral, gets value during connect 30 | private(set) var peripheral: CBPeripheral? 31 | 32 | /// for use in trace 33 | private let categoryBlueToothTransmitter = "xDripClient.BlueToothTransmitter" 34 | 35 | /// to be called when data is received or if there's a disconnect, this is the actual heartbeat. 36 | private let heartbeat : () -> () 37 | 38 | /// to be called whenever status change important for UI. In fact it can be called whenever there's in interaction with the CGM. 39 | private let onHeartBeatStatusChange: () -> () 40 | 41 | // MARK: - Initialization 42 | 43 | /// - parameters: 44 | /// - deviceAddress : the bluetooth Mac address 45 | /// - one serviceCBUUID: as string, this is the service to be discovered 46 | /// - CBUUID_Receive: receive characteristic uuid as string, to which subscribe should be done 47 | /// - onHeartBeatStatusChange : function to call when heartBeat related status changes. This is just to be able to change the UI. Eg when status goes from scanning to connected 48 | /// - heartbeat : function to call when data is received on the receive characteristic or when there's a disconnect 49 | init(deviceAddress: String, servicesCBUUID: String, CBUUID_Receive:String, onHeartBeatStatusChange: @escaping () -> (), heartbeat : @escaping () -> ()) { 50 | 51 | self.servicesCBUUIDs = [CBUUID(string: servicesCBUUID)] 52 | 53 | self.CBUUID_ReceiveCharacteristic = CBUUID_Receive 54 | 55 | self.deviceAddress = deviceAddress 56 | 57 | self.heartbeat = heartbeat 58 | 59 | self.onHeartBeatStatusChange = onHeartBeatStatusChange 60 | 61 | let cBCentralManagerOptionRestoreIdentifierKeyToUse = "Loop-" + deviceAddress 62 | 63 | super.init() 64 | 65 | trace("in initialize, creating centralManager for peripheral with address %{public}@", category: categoryBlueToothTransmitter, deviceAddress) 66 | 67 | centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true, CBCentralManagerOptionRestoreIdentifierKey: cBCentralManagerOptionRestoreIdentifierKeyToUse]) 68 | 69 | // connect to the device 70 | connect() 71 | 72 | } 73 | 74 | // MARK: - De-initialization 75 | 76 | deinit { 77 | 78 | trace("deinit called", category: categoryBlueToothTransmitter) 79 | 80 | // disconnect the device 81 | disconnect() 82 | 83 | } 84 | 85 | // MARK: - public functions 86 | 87 | /// will try to connect to the device, first by calling retrievePeripherals, if peripheral not known, then by calling startScanning 88 | func connect() { 89 | 90 | if !retrievePeripherals(centralManager!) { 91 | 92 | startScanning() 93 | 94 | } 95 | 96 | } 97 | 98 | /// disconnect the device 99 | func disconnect() { 100 | 101 | if let peripheral = peripheral { 102 | 103 | var name = "unknown" 104 | if let peripheralName = peripheral.name { 105 | name = peripheralName 106 | } 107 | 108 | trace("disconnecting from peripheral with name %{public}@", category: categoryBlueToothTransmitter, name) 109 | 110 | centralManager!.cancelPeripheralConnection(peripheral) 111 | 112 | } 113 | 114 | } 115 | 116 | /// stops scanning 117 | func stopScanning() { 118 | 119 | trace("in stopScanning", category: categoryBlueToothTransmitter) 120 | 121 | self.centralManager!.stopScan() 122 | 123 | } 124 | 125 | /// calls setNotifyValue for characteristic with value enabled 126 | func setNotifyValue(_ enabled: Bool, for characteristic: CBCharacteristic) { 127 | 128 | if let peripheral = peripheral { 129 | 130 | trace("setNotifyValue, for peripheral with name %{public}@, setting notify for characteristic %{public}@, to %{public}@", category: categoryBlueToothTransmitter, peripheral.name ?? "'unknown'", characteristic.uuid.uuidString, enabled.description) 131 | peripheral.setNotifyValue(enabled, for: characteristic) 132 | 133 | } else { 134 | 135 | trace("setNotifyValue, for peripheral with name %{public}@, failed to set notify for characteristic %{public}@, to %{public}@", category: categoryBlueToothTransmitter, peripheral?.name ?? "'unknown'", characteristic.uuid.uuidString, enabled.description) 136 | 137 | } 138 | } 139 | 140 | // MARK: - fileprivate functions 141 | 142 | /// start bluetooth scanning for device 143 | fileprivate func startScanning() { 144 | 145 | if centralManager!.state == .poweredOn { 146 | 147 | trace("in startScanning", category: categoryBlueToothTransmitter) 148 | 149 | centralManager!.scanForPeripherals(withServices: nil, options: nil) 150 | 151 | } else { 152 | 153 | trace("in startScanning. Not started, state is not poweredOn", category: categoryBlueToothTransmitter) 154 | 155 | } 156 | 157 | 158 | } 159 | 160 | /// stops scanning and connect. To be called after diddiscover 161 | fileprivate func stopScanAndconnect(to peripheral: CBPeripheral) { 162 | 163 | self.centralManager!.stopScan() 164 | 165 | self.peripheral = peripheral 166 | 167 | // will change info in UI 168 | onHeartBeatStatusChange() 169 | 170 | peripheral.delegate = self 171 | 172 | if peripheral.state == .disconnected { 173 | 174 | trace(" trying to connect", category: categoryBlueToothTransmitter) 175 | 176 | centralManager!.connect(peripheral, options: nil) 177 | 178 | } else { 179 | 180 | trace(" calling centralManager(newCentralManager, didConnect: peripheral", category: categoryBlueToothTransmitter) 181 | 182 | centralManager(centralManager!, didConnect: peripheral) 183 | 184 | } 185 | 186 | } 187 | 188 | /// try to connect to peripheral to which connection was successfully done previously, and that has a uuid that matches the stored deviceAddress. If such peripheral exists, then try to connect, it's not necessary to start scanning. iOS will connect as soon as the peripheral comes in range, or bluetooth status is switched on, whatever is necessary 189 | /// 190 | /// the result of the attempt to try to find such device, is returned 191 | fileprivate func retrievePeripherals(_ central:CBCentralManager) -> Bool { 192 | 193 | trace("in retrievePeripherals, deviceaddress is %{public}@", category: categoryBlueToothTransmitter, deviceAddress) 194 | 195 | if let uuid = UUID(uuidString: deviceAddress) { 196 | 197 | trace(" uuid is not nil", category: categoryBlueToothTransmitter) 198 | 199 | let peripheralArr = central.retrievePeripherals(withIdentifiers: [uuid]) 200 | 201 | if peripheralArr.count > 0 { 202 | 203 | peripheral = peripheralArr[0] 204 | 205 | // peripheral is assigned a value, heartbeat status text in UI must change 206 | onHeartBeatStatusChange() 207 | 208 | if let peripheral = peripheral { 209 | 210 | trace(" trying to connect", category: categoryBlueToothTransmitter) 211 | 212 | peripheral.delegate = self 213 | 214 | central.connect(peripheral, options: nil) 215 | 216 | return true 217 | 218 | } else { 219 | 220 | trace(" peripheral is nil", category: categoryBlueToothTransmitter) 221 | 222 | } 223 | } else { 224 | 225 | trace(" uuid is not nil, but central.retrievePeripherals returns 0 peripherals", category: categoryBlueToothTransmitter) 226 | 227 | } 228 | 229 | } else { 230 | 231 | trace(" uuid is nil", category: categoryBlueToothTransmitter) 232 | 233 | } 234 | 235 | return false 236 | 237 | } 238 | 239 | // MARK: - methods from protocols CBCentralManagerDelegate, CBPeripheralDelegate 240 | 241 | func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { 242 | 243 | // devicename needed unwrapped for logging 244 | var deviceName = "unknown" 245 | if let temp = peripheral.name { 246 | deviceName = temp 247 | } 248 | 249 | trace("Did discover peripheral with name: %{public}@", category: categoryBlueToothTransmitter, String(describing: deviceName)) 250 | 251 | // check if stored address not nil, in which case we already connected before and we expect a full match with the already known device name 252 | if peripheral.identifier.uuidString == deviceAddress { 253 | 254 | trace(" stored address matches peripheral address, will try to connect", category: categoryBlueToothTransmitter) 255 | 256 | stopScanAndconnect(to: peripheral) 257 | 258 | } else { 259 | 260 | trace(" stored address does not match peripheral address, ignoring this device", category: categoryBlueToothTransmitter) 261 | 262 | } 263 | 264 | } 265 | 266 | func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { 267 | 268 | trace("connected to peripheral with name %{public}@", category: categoryBlueToothTransmitter, peripheral.name ?? "'unknown'") 269 | 270 | peripheral.discoverServices(servicesCBUUIDs) 271 | 272 | } 273 | 274 | func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { 275 | 276 | if let error = error { 277 | 278 | trace("failed to connect, for peripheral with name %{public}@, with error: %{public}@, will try again", category: categoryBlueToothTransmitter, peripheral.name ?? "'unknown'", error.localizedDescription) 279 | 280 | } else { 281 | 282 | trace("failed to connect, for peripheral with name %{public}@, will try again", category: categoryBlueToothTransmitter, peripheral.name ?? "'unknown'") 283 | 284 | } 285 | 286 | centralManager!.connect(peripheral, options: nil) 287 | 288 | } 289 | 290 | func centralManagerDidUpdateState(_ central: CBCentralManager) { 291 | 292 | trace("in centralManagerDidUpdateState, for peripheral with name %{public}@, new state is %{public}@", category: categoryBlueToothTransmitter, peripheral?.name ?? "'unknown'", "\(central.state.toString())") 293 | 294 | /// in case status changed to powered on and if device address known then try to retrieveperipherals 295 | if central.state == .poweredOn { 296 | 297 | /// try to connect to device to which connection was successfully done previously, this attempt is done by callling retrievePeripherals(central) 298 | _ = retrievePeripherals(central) 299 | 300 | } 301 | 302 | } 303 | 304 | func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { 305 | 306 | trace(" didDisconnect peripheral with name %{public}@", category: categoryBlueToothTransmitter , peripheral.name ?? "'unknown'") 307 | 308 | // call heartbeat, useful for Dexcom transmitters, after a disconnect, then there's probably a new reading available 309 | heartbeat() 310 | 311 | if let error = error { 312 | 313 | trace(" error: %{public}@", category: categoryBlueToothTransmitter, error.localizedDescription) 314 | 315 | } 316 | 317 | // if self.peripheral == nil, then a manual disconnect or something like that has occured, no need to reconnect 318 | // otherwise disconnect occurred because of other (like out of range), so let's try to reconnect 319 | if let ownPeripheral = self.peripheral { 320 | 321 | trace(" Will try to reconnect", category: categoryBlueToothTransmitter) 322 | 323 | centralManager!.connect(ownPeripheral, options: nil) 324 | 325 | } else { 326 | 327 | trace(" peripheral is nil, will not try to reconnect", category: categoryBlueToothTransmitter) 328 | 329 | } 330 | 331 | } 332 | 333 | func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { 334 | 335 | trace("didDiscoverServices for peripheral with name %{public}@", category: categoryBlueToothTransmitter, peripheral.name ?? "'unknown'") 336 | 337 | if let error = error { 338 | trace(" didDiscoverServices error: %{public}@", category: categoryBlueToothTransmitter, "\(error.localizedDescription)") 339 | } 340 | 341 | if let services = peripheral.services { 342 | for service in services { 343 | trace(" Call discovercharacteristics for service with uuid %{public}@", category: categoryBlueToothTransmitter, String(describing: service.uuid)) 344 | peripheral.discoverCharacteristics(nil, for: service) 345 | } 346 | } else { 347 | disconnect() 348 | } 349 | } 350 | 351 | func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { 352 | 353 | trace("didDiscoverCharacteristicsFor for peripheral with name %{public}@, for service with uuid %{public}@", category: categoryBlueToothTransmitter, peripheral.name ?? "'unknown'", String(describing:service.uuid)) 354 | 355 | if let error = error { 356 | trace(" didDiscoverCharacteristicsFor error: %{public}@", category: categoryBlueToothTransmitter, error.localizedDescription) 357 | } 358 | 359 | if let characteristics = service.characteristics { 360 | 361 | for characteristic in characteristics { 362 | 363 | trace(" characteristic: %{public}@", category: categoryBlueToothTransmitter, String(describing: characteristic.uuid)) 364 | 365 | if characteristic.uuid == CBUUID(string: CBUUID_ReceiveCharacteristic) { 366 | 367 | trace(" found receiveCharacteristic", category: categoryBlueToothTransmitter) 368 | 369 | receiveCharacteristic = characteristic 370 | 371 | peripheral.setNotifyValue(true, for: characteristic) 372 | 373 | } 374 | 375 | } 376 | 377 | } else { 378 | 379 | trace(" Did discover characteristics, but no characteristics listed. There must be some error.", category: categoryBlueToothTransmitter) 380 | 381 | } 382 | } 383 | 384 | func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { 385 | 386 | if let error = error { 387 | 388 | trace("didUpdateNotificationStateFor for peripheral with name %{public}@, characteristic %{public}@, error = %{public}@", category: categoryBlueToothTransmitter, peripheral.name ?? "'unkonwn'", String(describing: characteristic.uuid), error.localizedDescription) 389 | 390 | } 391 | 392 | } 393 | 394 | func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { 395 | 396 | trace("didUpdateValueFor for peripheral with name %{public}@", category: categoryBlueToothTransmitter, peripheral.name ?? "'unknown'") 397 | 398 | // call heartbeat 399 | heartbeat() 400 | 401 | } 402 | 403 | func centralManager(_ central: CBCentralManager, 404 | willRestoreState dict: [String : Any]) { 405 | 406 | // willRestoreState must be defined, otherwise the app would crash (because the centralManager was created with a CBCentralManagerOptionRestoreIdentifierKey) 407 | // even if it's an empty function 408 | // trace is called here because it allows us to see in the issue reports if there was a restart after app crash or removed from memory - in all other cases (force closed by user) this function is not called 409 | 410 | trace("in willRestoreState", category: categoryBlueToothTransmitter) 411 | 412 | } 413 | 414 | } 415 | 416 | 417 | -------------------------------------------------------------------------------- /xDripClient/xDripCGMManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // xDripCGMManager.swift 3 | // xDripClient 4 | // 5 | // Created by Julian Groen on 15/03/2022. 6 | // Copyright © 2022 Julian Groen. All rights reserved. 7 | // 8 | 9 | import LoopKit 10 | import LoopKitUI 11 | import HealthKit 12 | import Combine 13 | import Foundation 14 | 15 | 16 | public class xDripCGMManager: NSObject, CGMManager { 17 | 18 | public static var pluginIdentifier: String = "xDripClient" 19 | 20 | public var localizedTitle: String = "xDrip4iOS" 21 | 22 | public var providesBLEHeartbeat: Bool { 23 | get { 24 | return UserDefaults.standard.useCGMAsHeartbeat 25 | } 26 | } 27 | 28 | public var isOnboarded: Bool = true // No distinction between created and onboarded 29 | 30 | public var shouldSyncToRemoteService: Bool { 31 | get { 32 | return UserDefaults.standard.shouldSyncToRemoteService 33 | } 34 | } 35 | 36 | public let appURL: URL? = URL(string: "xdripswift://") 37 | 38 | public var managedDataInterval: TimeInterval? = nil 39 | 40 | public let delegate = WeakSynchronizedDelegate() 41 | 42 | public private(set) var latestReading: xDripReading? 43 | 44 | public var glucoseDisplay: GlucoseDisplayable? { 45 | return self.latestReading 46 | } 47 | 48 | public let sharedUserDefaults: xDripAppGroup = xDripAppGroup() 49 | 50 | public var cgmManagerDelegate: CGMManagerDelegate? { 51 | get { 52 | return delegate.delegate 53 | } 54 | set { 55 | delegate.delegate = newValue 56 | } 57 | } 58 | 59 | // needed to conform to protocol CGMManager 60 | public var delegateQueue: DispatchQueue! { 61 | get { 62 | return delegate.queue 63 | } 64 | set { 65 | delegate.queue = newValue 66 | } 67 | } 68 | 69 | public var cgmManagerStatus: CGMManagerStatus { 70 | return CGMManagerStatus(hasValidSensorSession: true, device: nil) 71 | } 72 | 73 | public var rawState: CGMManager.RawStateValue { 74 | return [:] 75 | } 76 | 77 | /// - instance of bluetoothTransmitter that will connect to the CGM, with goal to achieve heartbeat mechanism, nothing else 78 | /// - if nil then there's no heartbeat generated 79 | private var bluetoothTransmitter: BluetoothTransmitter? 80 | 81 | /// when was the last time that readings where fetched from shared userdefaults 82 | private var timeStampLastFetch:Date = Date(timeIntervalSince1970: 0) 83 | 84 | /// for use in trace 85 | private let categoryxDripCGMManager = "xDripClient.xDripCGMManager" 86 | 87 | /// define notification center, to be informed when app comes in background, so that fetchNewData can be forced 88 | let notificationCenter = NotificationCenter.default 89 | 90 | public override init() { 91 | 92 | // call super.init 93 | super.init() 94 | 95 | /// add observer for will enter foreground 96 | notificationCenter.addObserver(self, selector: #selector(runWhenAppWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) 97 | 98 | // add observer for did finish launching 99 | notificationCenter.addObserver(self, selector: #selector(runWhenAppWillEnterForeground(_:)), name: UIApplication.didFinishLaunchingNotification, object: nil) 100 | 101 | // add observer when going to background 102 | notificationCenter.addObserver(self, selector: #selector(runWhenAppWillEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil) 103 | 104 | // add observer for useCGMAsHeartbeat 105 | UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.useCGMAsHeartbeat.rawValue, options: .new, context: nil) 106 | 107 | // possibly cgmTransmitterDeviceAddess in shared user defaults has been changed by xDrip4iOS while Loop was not running. Reassign the value in UserDefaults 108 | UserDefaults.standard.cgmTransmitterDeviceAddress = sharedUserDefaults.cgmTransmitterDeviceAddress 109 | 110 | // add observer for shared userdefaults key cgmTransmitterDeviceAddress 111 | sharedUserDefaults.sharedUserDefaults?.addObserver(self, forKeyPath: xDripAppGroup.keyForcgmTransmitterDeviceAddress, context: nil) 112 | 113 | // see if bluetoothTransmitter needs to be instantiated 114 | // if return value nil, then bluetoothTransmitter will be set to nil, means als if any connection would already be existing, then it will be disconnected 115 | bluetoothTransmitter = setupBluetoothTransmitter() 116 | 117 | // set heartbeat state text in userdefaults, this is used in the UI 118 | setHeartbeatStateTextAndIsIdleTimerDisabled() 119 | 120 | } 121 | 122 | public required convenience init?(rawState: RawStateValue) { 123 | self.init() 124 | } 125 | 126 | public func fetchNewDataIfNeeded(_ completion: @escaping (CGMReadingResult) -> Void) { 127 | 128 | // there should be at least 1 minute between two fetches 129 | guard timeStampLastFetch.timeIntervalSinceNow < TimeInterval(-55.0) else {return} 130 | 131 | // check if bluetoothTransmitter is still valid - used for heartbeating 132 | checkCGMBluetoothTransmitter() 133 | 134 | trace("in fetchNewDataIfNeeded", category: categoryxDripCGMManager) 135 | 136 | timeStampLastFetch = Date() 137 | 138 | do { 139 | 140 | let readings = try sharedUserDefaults.fetchLatestReadings() 141 | 142 | guard readings.isEmpty == false else { 143 | trace(" readings.isEmpty is true", category: self.categoryxDripCGMManager) 144 | self.delegate.notify { (delegate) in delegate?.cgmManager(self, hasNew: .noData) } 145 | return 146 | } 147 | 148 | var startDate = Date(timeIntervalSinceNow: -TimeInterval(30*60)) 149 | if let latestReading = latestReading { 150 | if startDate.timeIntervalSince(latestReading.startDate) < 30*60 { 151 | startDate = latestReading.startDate 152 | } 153 | } 154 | 155 | 156 | let newGlucoseSamples = readings.filterDateRange(startDate, nil).map { 157 | NewGlucoseSample(date: $0.startDate, quantity: $0.quantity, 158 | condition: nil, trend: $0.trendType, trendRate: $0.trendRate, 159 | isDisplayOnly: false, wasUserEntered: false, 160 | syncIdentifier: "\(Int($0.startDate.timeIntervalSince1970))") 161 | } 162 | 163 | self.delegate.notify { (delegate) in 164 | delegate?.cgmManager(self, hasNew: newGlucoseSamples.isEmpty ? .noData : .newData(newGlucoseSamples)) 165 | } 166 | 167 | self.latestReading = readings.max(by: { $0.startDate < $1.startDate }) 168 | 169 | } catch let error { 170 | 171 | if let error = error as? xDripAppGroup.AppGroupError { 172 | 173 | switch error { 174 | case .data (let text): 175 | trace("in fetchNewDataIfNeeded, failed to get readings, error = %{public}@", category: categoryxDripCGMManager, text) 176 | } 177 | 178 | } else { 179 | trace("in fetchNewDataIfNeeded, failed to get readings", category: categoryxDripCGMManager) 180 | } 181 | 182 | self.delegate.notify { (delegate) in delegate?.cgmManager(self, hasNew: .noData) } 183 | 184 | } 185 | 186 | } 187 | 188 | public override var debugDescription: String { 189 | return [ 190 | "## xDripCGMManager", 191 | "latestReading: \(String(describing: latestReading))", 192 | "" 193 | ].joined(separator: "\n") 194 | } 195 | 196 | // override to observe useCGMAsHeartbeat and keyForcgmTransmitterDeviceAddress 197 | public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 198 | 199 | if let keyPath = keyPath { 200 | 201 | if let keyPathEnum = UserDefaults.Key(rawValue: keyPath) { 202 | 203 | switch keyPathEnum { 204 | 205 | case UserDefaults.Key.useCGMAsHeartbeat : 206 | bluetoothTransmitter = setupBluetoothTransmitter() 207 | 208 | setHeartbeatStateTextAndIsIdleTimerDisabled() 209 | 210 | default: 211 | break 212 | } 213 | } else { 214 | 215 | if keyPath == xDripAppGroup.keyForcgmTransmitterDeviceAddress { 216 | 217 | checkCGMBluetoothTransmitter() 218 | 219 | setHeartbeatStateTextAndIsIdleTimerDisabled() 220 | 221 | } 222 | 223 | } 224 | } 225 | } 226 | 227 | /// will call fetchNewDataIfNeeded with completionhandler 228 | /// used as wakeup function 229 | private func fetchNewDataIfNeeded() { 230 | 231 | self.fetchNewDataIfNeeded { result in 232 | // no need to process the result, it's already processed in fetchNewDataIfNeeded and sent to delegate 233 | } 234 | 235 | } 236 | 237 | /// check if a new bluetoothTransmitter needs to be assigned and if yes, assign it 238 | private func checkCGMBluetoothTransmitter() { 239 | 240 | if UserDefaults.standard.cgmTransmitterDeviceAddress != sharedUserDefaults.cgmTransmitterDeviceAddress { 241 | 242 | // assign new bluetoothTransmitter. If return value is nil, and if it was not nil before, and if it was currently connected then it will disconnect automatically, because there's no other reference to it, hence deinit will be called 243 | bluetoothTransmitter = setupBluetoothTransmitter() 244 | 245 | // assign local copy of cgmTransmitterDeviceAddress to the value stored in sharedUserDefaults (possibly nil value) 246 | UserDefaults.standard.cgmTransmitterDeviceAddress = sharedUserDefaults.cgmTransmitterDeviceAddress 247 | 248 | } 249 | 250 | /// change might be required to text 251 | setHeartbeatStateTextAndIsIdleTimerDisabled() 252 | 253 | } 254 | 255 | /// if UserDefaults.standard.useCGMAsHeartbeat is true and sharedUserDefaults.cgmTransmitterDeviceAddress then create new BluetoothTransmitter 256 | private func setupBluetoothTransmitter() -> BluetoothTransmitter? { 257 | 258 | // if sharedUserDefaults.cgmTransmitterDeviceAddress is not nil then, create a new bluetoothTranmsitter instance 259 | if UserDefaults.standard.useCGMAsHeartbeat, let cgmTransmitterDeviceAddress = sharedUserDefaults.cgmTransmitterDeviceAddress { 260 | 261 | // unwrap cgmTransmitter_CBUUID_Service and cgmTransmitter_CBUUID_Receive 262 | if let cgmTransmitter_CBUUID_Service = sharedUserDefaults.cgmTransmitter_CBUUID_Service, let cgmTransmitter_CBUUID_Receive = sharedUserDefaults.cgmTransmitter_CBUUID_Receive { 263 | 264 | // a new cgm transmitter has been setup in xDrip4iOS 265 | // we will connect to the same transmitter here so it can be used as heartbeat 266 | let newBluetoothTransmitter = BluetoothTransmitter(deviceAddress: cgmTransmitterDeviceAddress, servicesCBUUID: cgmTransmitter_CBUUID_Service, CBUUID_Receive: cgmTransmitter_CBUUID_Receive, onHeartBeatStatusChange: setHeartbeatStateTextAndIsIdleTimerDisabled, heartbeat: fetchNewDataIfNeeded) 267 | 268 | return newBluetoothTransmitter 269 | 270 | } else { 271 | 272 | trace("in checkCGMBluetoothTransmitter, looks like a coding error, xdrip4iOS did set a value for cgmTransmitterDeviceAddress in sharedUserDefaults but did not set a value for cgmTransmitter_CBUUID_Service or cgmTransmitter_CBUUID_Receive", category: categoryxDripCGMManager) 273 | 274 | return nil 275 | 276 | } 277 | 278 | } 279 | 280 | return nil 281 | 282 | } 283 | 284 | /// will set text in UserDefaults heartBeatState depending on BluetoothTransmitter status, this is then used in UI. 285 | /// Also sets UIApplication.shared.isIdleTimerDisabled depending on whether Loop is scanning for CGM or not 286 | private func setHeartbeatStateTextAndIsIdleTimerDisabled() { 287 | 288 | let scanning = LocalizedString("Scanning for CGM. Force close xDrip4iOS (do not disconnect but force close the app). Keep Loop running in the foreground (prevent phone lock). This text will change as soon as a first connection is made. ", comment: "This is when Loop did not yet make a first connection to the CGM. It is scanning. Need to make sure that no other app (like xDrip4iOS) is connected to the CGM") 289 | 290 | let firstConnectionMade = LocalizedString("Did connect to CGM. You can now run both xDrip4iOS and Loop. The CGM will be used as heartbeat for Loop.", comment: "Did connect to CGM. Even though it's not connected now, this state remains valid. The CGM will be used as heartbeat for Loop.") 291 | 292 | let cgmUnknown = LocalizedString("You first need to have made a successful connection between xDrip4iOS and the CGM. Force close Loop, open xDrip4iOS and make sure it's connected to the CGM. Once done, Force close xDrip4iOS (do not disconnect but force close the app), open Loop and come back to here", comment: "There hasn't been a connectin to xDrip4iOS to the CGM. First need to have a made a successful connection between xDrip4iOS and the CGM. Force close Loop, open xDrip4iOS and make sure it's connected to the CGM. Once done, Force close xDrip4iOS (do not disconnect but force close the app), open Loop and come back to here") 293 | 294 | // this is for example in case user has selected not to use the CGM as heartbeat. In that case the UI should not even show this text. Meaning normally it should never be shown 295 | let notapplicable = "N/A" 296 | 297 | // in case user has selected not to use cgm as heartbeat 298 | if !UserDefaults.standard.useCGMAsHeartbeat { 299 | UserDefaults.standard.heartBeatState = notapplicable 300 | return 301 | } 302 | 303 | // in case xDrip4iOS did not make a first connection to the CGM (or explicitly disconnected from the CGM) 304 | if UserDefaults.standard.cgmTransmitterDeviceAddress == nil { 305 | UserDefaults.standard.heartBeatState = cgmUnknown 306 | return 307 | } 308 | 309 | // now there should be a bluetoothTransmitter, if not there's a coding error 310 | guard let bluetoothTransmitter = bluetoothTransmitter else { 311 | UserDefaults.standard.heartBeatState = notapplicable 312 | return 313 | } 314 | 315 | // if peripheral in bluetoothTransmitter is still nil, then it means Loop is still scanning for the CGM, it didn't make a first connection yet 316 | if bluetoothTransmitter.peripheral == nil { 317 | UserDefaults.standard.heartBeatState = scanning 318 | return 319 | } 320 | 321 | // in all other cases, the state should be ok 322 | UserDefaults.standard.heartBeatState = firstConnectionMade 323 | 324 | } 325 | 326 | @objc private func runWhenAppWillEnterForeground(_ : Notification) { 327 | 328 | fetchNewDataIfNeeded() 329 | 330 | } 331 | 332 | @objc private func runWhenAppWillEnterBackground(_ : Notification) { 333 | 334 | 335 | if UserDefaults.standard.screenLockedByxDrip4iOSClient { 336 | 337 | // prevent screen dim/lock 338 | UIApplication.shared.isIdleTimerDisabled = false 339 | 340 | UserDefaults.standard.screenLockedByxDrip4iOSClient = false 341 | 342 | } 343 | 344 | } 345 | 346 | } 347 | // MARK: - AlertResponder implementation 348 | extension xDripCGMManager { 349 | public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) { 350 | completion(nil) 351 | } 352 | } 353 | 354 | // MARK: - AlertSoundVendor implementation 355 | extension xDripCGMManager { 356 | public func getSoundBaseURL() -> URL? { return nil } 357 | public func getSounds() -> [Alert.Sound] { return [] } 358 | } 359 | 360 | // MARK: - UserDefaults 361 | 362 | extension UserDefaults { 363 | 364 | public enum Key: String { 365 | 366 | /// used as local copy of cgmTransmitterDeviceAddress, will be compared regularly against value in shared UserDefaults 367 | /// 368 | /// this is the local stored (ie not shared with xDrip4iOS) copy of the cgm (bluetooth) device address 369 | case cgmTransmitterDeviceAddress = "com.loopkit.Loop.cgmTransmitterDeviceAddress" 370 | 371 | /// did user ask heartbeat from CGM that is used by xDrip4iOS, default false 372 | case useCGMAsHeartbeat = "useCGMAsHeartbeat" 373 | 374 | /// status of Loop vs CGM, this is text shown to user in UI. Text shows the status of heartbeat 375 | case heartBeatState = "heartBeatState" 376 | 377 | /// should Loop upload bg readings to remote service or not. Default false 378 | /// 379 | /// Used in Loop/Managers/RemoteDataServicesManager.swift, func uploadGlucoseData(to remoteDataService: RemoteDataService)namic public var shouldSyncToRemoteService: Boo 380 | case shouldSyncToRemoteService = "shouldSyncToRemoteService" 381 | 382 | /// there's a screen locking feature, this is used to know if it as the xdrip4ios client that locked the screen 383 | case screenLockedByxDrip4iOSClient = "screenLockedByxDrip4iOSClient" 384 | 385 | } 386 | 387 | /// there's a screen locking feature, this is used to know if it as the xdrip4ios client that locked the screen 388 | @objc dynamic public var screenLockedByxDrip4iOSClient: Bool { 389 | 390 | get { 391 | return bool(forKey: Key.screenLockedByxDrip4iOSClient.rawValue) 392 | } 393 | set { 394 | set(newValue, forKey: Key.screenLockedByxDrip4iOSClient.rawValue) 395 | } 396 | } 397 | 398 | /// should Loop upload bg readings to remote service or not. Default false 399 | /// 400 | /// Used in Loop/Managers/RemoteDataServicesManager.swift, func uploadGlucoseData(to remoteDataService: RemoteDataService) 401 | @objc dynamic public var shouldSyncToRemoteService: Bool { 402 | 403 | // default value for bool in userdefaults is false 404 | get { 405 | return bool(forKey: Key.shouldSyncToRemoteService.rawValue) 406 | } 407 | set { 408 | set(newValue, forKey: Key.shouldSyncToRemoteService.rawValue) 409 | } 410 | 411 | } 412 | 413 | /// used as local copy of cgmTransmitterDeviceAddress, will be compared regularly against value in shared UserDefaults 414 | var cgmTransmitterDeviceAddress: String? { 415 | get { 416 | return string(forKey: Key.cgmTransmitterDeviceAddress.rawValue) 417 | } 418 | set { 419 | set(newValue, forKey: Key.cgmTransmitterDeviceAddress.rawValue) 420 | } 421 | } 422 | 423 | /// did user ask heartbeat from CGM that is used by xDrip4iOS, default : true 424 | @objc dynamic var useCGMAsHeartbeat: Bool { 425 | 426 | // default value for bool in userdefaults is false, by default we want to use heartbeat 427 | get { 428 | return bool(forKey: Key.useCGMAsHeartbeat.rawValue) 429 | } 430 | set { 431 | set(newValue, forKey: Key.useCGMAsHeartbeat.rawValue) 432 | } 433 | 434 | } 435 | 436 | /// status of Loop vs CGM, this is text shown to user in UI. Text shows the status of heartbeat 437 | @objc dynamic var heartBeatState: String { 438 | 439 | // default value for bool in userdefaults is false, by default we want to use heartbeat 440 | get { 441 | return string(forKey: Key.heartBeatState.rawValue) ?? "" 442 | } 443 | set { 444 | set(newValue, forKey: Key.heartBeatState.rawValue) 445 | } 446 | 447 | } 448 | 449 | } 450 | 451 | -------------------------------------------------------------------------------- /xDripClient.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A53987527E0C10E00077BAF /* xDripStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53987427E0C10E00077BAF /* xDripStatusView.swift */; }; 11 | 0A53987727E0C13900077BAF /* xDripStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53987627E0C13900077BAF /* xDripStatusModel.swift */; }; 12 | 0A53987927E0C90500077BAF /* xDripClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 84752E8226ED0FFE009FD801 /* xDripClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 13 | 0A53987A27E0C90B00077BAF /* xDripClientUI.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0AA1BFD327E030190098B986 /* xDripClientUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 0A53987C27E0CA0100077BAF /* Framework.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53987B27E0CA0100077BAF /* Framework.swift */; }; 15 | 0A53987D27E0CA0100077BAF /* Framework.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53987B27E0CA0100077BAF /* Framework.swift */; }; 16 | 0A53987F27E0CA2600077BAF /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53987E27E0CA2600077BAF /* UIImage.swift */; }; 17 | 0A53988127E0CA5200077BAF /* xDripClientUI.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A53988027E0CA5200077BAF /* xDripClientUI.xcassets */; }; 18 | 0A53988327E0CCB700077BAF /* xDripAppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53988227E0CCB700077BAF /* xDripAppGroup.swift */; }; 19 | 0A53988527E0ECE100077BAF /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53988427E0ECE100077BAF /* LocalizedString.swift */; }; 20 | 0A53988627E0ECE200077BAF /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53988427E0ECE100077BAF /* LocalizedString.swift */; }; 21 | 0A53988927E0EFE100077BAF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0A53988727E0EFE100077BAF /* Localizable.strings */; }; 22 | 0AA1BFC627E02D770098B986 /* xDripClientPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFC427E02D770098B986 /* xDripClientPlugin.swift */; }; 23 | 0AA1BFC827E02DC40098B986 /* xDripCGMManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFC727E02DC40098B986 /* xDripCGMManager.swift */; }; 24 | 0AA1BFC927E02ECB0098B986 /* xDripClientPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AA1BFC327E02D770098B986 /* xDripClientPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25 | 0AA1BFCD27E02F540098B986 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1BFCC27E02F530098B986 /* HealthKit.framework */; }; 26 | 0AA1BFDA27E0304B0098B986 /* xDripClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84752E8226ED0FFE009FD801 /* xDripClient.framework */; }; 27 | 0AA1BFE027E031060098B986 /* xDripClientUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AA1BFDF27E031060098B986 /* xDripClientUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28 | 0AA1BFE327E032920098B986 /* xDripCGMManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFE127E032910098B986 /* xDripCGMManager+UI.swift */; }; 29 | 0AA1BFE427E032920098B986 /* UICoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFE227E032910098B986 /* UICoordinator.swift */; }; 30 | 0AA1BFEE27E0354B0098B986 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFED27E0354B0098B986 /* OSLog.swift */; }; 31 | 0AA1BFEF27E0357E0098B986 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFED27E0354B0098B986 /* OSLog.swift */; }; 32 | 0AA1BFF227E035E60098B986 /* xDripClientUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1BFD327E030190098B986 /* xDripClientUI.framework */; }; 33 | 0AA1BFF427E03ACE0098B986 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFED27E0354B0098B986 /* OSLog.swift */; }; 34 | 0AA1BFF627E0BCE00098B986 /* xDripReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFF527E0BCE00098B986 /* xDripReading.swift */; }; 35 | 0AA1BFF827E0BD9B0098B986 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFF727E0BD9B0098B986 /* HKUnit.swift */; }; 36 | 0AA1BFF927E0BD9C0098B986 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA1BFF727E0BD9B0098B986 /* HKUnit.swift */; }; 37 | 84752E9326ED0FFE009FD801 /* xDripClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 84752E8526ED0FFE009FD801 /* xDripClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; 38 | 847530F626ED65DD009FD801 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 847530F426ED65DD009FD801 /* LoopKit.framework */; }; 39 | 847530F826ED65DD009FD801 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 847530F526ED65DD009FD801 /* LoopKitUI.framework */; }; 40 | C1D27A1527908DC600C41EBA /* xDripClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84752E8226ED0FFE009FD801 /* xDripClient.framework */; }; 41 | F87C2BB827E675810053D946 /* BluetoothTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87C2BB727E675810053D946 /* BluetoothTransmitter.swift */; }; 42 | F87C2BBA27E68CAA0053D946 /* Trace.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87C2BB927E68CAA0053D946 /* Trace.swift */; }; 43 | F87C2BBD27E68F9D0053D946 /* StringProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87C2BBC27E68F9D0053D946 /* StringProtocol.swift */; }; 44 | F87C2BBF27E7DEE70053D946 /* CBManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87C2BBE27E7DEE70053D946 /* CBManagerState.swift */; }; 45 | F87C2BC127EA74260053D946 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87C2BC027EA74260053D946 /* URL.swift */; }; 46 | F8CB9E65282FDB20008E8EA1 /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB9E63282FDABD008E8EA1 /* MailView.swift */; }; 47 | F8CB9E67282FE343008E8EA1 /* ConstantsxDripClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB9E66282FE343008E8EA1 /* ConstantsxDripClient.swift */; }; 48 | /* End PBXBuildFile section */ 49 | 50 | /* Begin PBXContainerItemProxy section */ 51 | 0AA1BFDC27E0304B0098B986 /* PBXContainerItemProxy */ = { 52 | isa = PBXContainerItemProxy; 53 | containerPortal = 84752E7926ED0FFE009FD801 /* Project object */; 54 | proxyType = 1; 55 | remoteGlobalIDString = 84752E8126ED0FFE009FD801; 56 | remoteInfo = xDripClient; 57 | }; 58 | 0AA1BFF027E035DF0098B986 /* PBXContainerItemProxy */ = { 59 | isa = PBXContainerItemProxy; 60 | containerPortal = 84752E7926ED0FFE009FD801 /* Project object */; 61 | proxyType = 1; 62 | remoteGlobalIDString = 0AA1BFD227E030190098B986; 63 | remoteInfo = xDripClientUI; 64 | }; 65 | C187C19F279086FF006E3557 /* PBXContainerItemProxy */ = { 66 | isa = PBXContainerItemProxy; 67 | containerPortal = 84752E7926ED0FFE009FD801 /* Project object */; 68 | proxyType = 1; 69 | remoteGlobalIDString = 84752E8126ED0FFE009FD801; 70 | remoteInfo = OmniBLE; 71 | }; 72 | /* End PBXContainerItemProxy section */ 73 | 74 | /* Begin PBXCopyFilesBuildPhase section */ 75 | 0A53987827E0C8DE00077BAF /* CopyFiles */ = { 76 | isa = PBXCopyFilesBuildPhase; 77 | buildActionMask = 2147483647; 78 | dstPath = ""; 79 | dstSubfolderSpec = 10; 80 | files = ( 81 | 0A53987A27E0C90B00077BAF /* xDripClientUI.framework in CopyFiles */, 82 | 0A53987927E0C90500077BAF /* xDripClient.framework in CopyFiles */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXCopyFilesBuildPhase section */ 87 | 88 | /* Begin PBXFileReference section */ 89 | 0A53987427E0C10E00077BAF /* xDripStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xDripStatusView.swift; sourceTree = ""; }; 90 | 0A53987627E0C13900077BAF /* xDripStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xDripStatusModel.swift; sourceTree = ""; }; 91 | 0A53987B27E0CA0100077BAF /* Framework.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Framework.swift; sourceTree = ""; }; 92 | 0A53987E27E0CA2600077BAF /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; 93 | 0A53988027E0CA5200077BAF /* xDripClientUI.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = xDripClientUI.xcassets; sourceTree = ""; }; 94 | 0A53988227E0CCB700077BAF /* xDripAppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xDripAppGroup.swift; sourceTree = ""; }; 95 | 0A53988427E0ECE100077BAF /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; 96 | 0A53988827E0EFE100077BAF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 97 | 0A5398AE27E0F03600077BAF /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; 98 | 0A5398AF27E0F04A00077BAF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 99 | 0A5398B027E0F05A00077BAF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 100 | 0A5398B327E0F0B900077BAF /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 101 | 0AA1BFC327E02D770098B986 /* xDripClientPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xDripClientPlugin.h; sourceTree = ""; }; 102 | 0AA1BFC427E02D770098B986 /* xDripClientPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = xDripClientPlugin.swift; sourceTree = ""; }; 103 | 0AA1BFC727E02DC40098B986 /* xDripCGMManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xDripCGMManager.swift; sourceTree = ""; }; 104 | 0AA1BFCC27E02F530098B986 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/iOSSupport/System/Library/Frameworks/HealthKit.framework; sourceTree = DEVELOPER_DIR; }; 105 | 0AA1BFD327E030190098B986 /* xDripClientUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = xDripClientUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 106 | 0AA1BFDF27E031060098B986 /* xDripClientUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xDripClientUI.h; sourceTree = ""; }; 107 | 0AA1BFE127E032910098B986 /* xDripCGMManager+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "xDripCGMManager+UI.swift"; sourceTree = ""; }; 108 | 0AA1BFE227E032910098B986 /* UICoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICoordinator.swift; sourceTree = ""; }; 109 | 0AA1BFE527E032EC0098B986 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 110 | 0AA1BFE927E0344A0098B986 /* carthage.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = carthage.sh; sourceTree = ""; }; 111 | 0AA1BFEA27E0344A0098B986 /* copy-frameworks.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "copy-frameworks.sh"; sourceTree = ""; }; 112 | 0AA1BFED27E0354B0098B986 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; 113 | 0AA1BFF527E0BCE00098B986 /* xDripReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xDripReading.swift; sourceTree = ""; }; 114 | 0AA1BFF727E0BD9B0098B986 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; 115 | 84752E8226ED0FFE009FD801 /* xDripClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = xDripClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 116 | 84752E8526ED0FFE009FD801 /* xDripClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xDripClient.h; sourceTree = ""; }; 117 | 84752E8626ED0FFE009FD801 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 118 | 847530F426ED65DD009FD801 /* LoopKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 119 | 847530F526ED65DD009FD801 /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 120 | A91B2DEB276E1E5E001B0E95 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 121 | C187C197279086A8006E3557 /* xDripClientPlugin.loopplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = xDripClientPlugin.loopplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 122 | C187C1A427908B1C006E3557 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 123 | F87C2BB727E675810053D946 /* BluetoothTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitter.swift; sourceTree = ""; }; 124 | F87C2BB927E68CAA0053D946 /* Trace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Trace.swift; sourceTree = ""; }; 125 | F87C2BBC27E68F9D0053D946 /* StringProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringProtocol.swift; sourceTree = ""; }; 126 | F87C2BBE27E7DEE70053D946 /* CBManagerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBManagerState.swift; sourceTree = ""; }; 127 | F87C2BC027EA74260053D946 /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 128 | F8CB9E63282FDABD008E8EA1 /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = ""; }; 129 | F8CB9E66282FE343008E8EA1 /* ConstantsxDripClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsxDripClient.swift; sourceTree = ""; }; 130 | /* End PBXFileReference section */ 131 | 132 | /* Begin PBXFrameworksBuildPhase section */ 133 | 0AA1BFD027E030190098B986 /* Frameworks */ = { 134 | isa = PBXFrameworksBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | 0AA1BFDA27E0304B0098B986 /* xDripClient.framework in Frameworks */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | 84752E7F26ED0FFE009FD801 /* Frameworks */ = { 142 | isa = PBXFrameworksBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | 0AA1BFCD27E02F540098B986 /* HealthKit.framework in Frameworks */, 146 | 847530F626ED65DD009FD801 /* LoopKit.framework in Frameworks */, 147 | 847530F826ED65DD009FD801 /* LoopKitUI.framework in Frameworks */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | C187C194279086A8006E3557 /* Frameworks */ = { 152 | isa = PBXFrameworksBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | 0AA1BFF227E035E60098B986 /* xDripClientUI.framework in Frameworks */, 156 | C1D27A1527908DC600C41EBA /* xDripClient.framework in Frameworks */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXFrameworksBuildPhase section */ 161 | 162 | /* Begin PBXGroup section */ 163 | 0AA1BFD427E030190098B986 /* xDripClientUI */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | F8CB9E63282FDABD008E8EA1 /* MailView.swift */, 167 | 0A53988727E0EFE100077BAF /* Localizable.strings */, 168 | 0AA1BFE227E032910098B986 /* UICoordinator.swift */, 169 | 0AA1BFE127E032910098B986 /* xDripCGMManager+UI.swift */, 170 | 0A53987427E0C10E00077BAF /* xDripStatusView.swift */, 171 | 0A53987627E0C13900077BAF /* xDripStatusModel.swift */, 172 | 0AA1BFDF27E031060098B986 /* xDripClientUI.h */, 173 | 0A53988027E0CA5200077BAF /* xDripClientUI.xcassets */, 174 | 0AA1BFE527E032EC0098B986 /* Info.plist */, 175 | ); 176 | path = xDripClientUI; 177 | sourceTree = ""; 178 | }; 179 | 0AA1BFE827E0344A0098B986 /* Scripts */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 0AA1BFE927E0344A0098B986 /* carthage.sh */, 183 | 0AA1BFEA27E0344A0098B986 /* copy-frameworks.sh */, 184 | ); 185 | path = Scripts; 186 | sourceTree = ""; 187 | }; 188 | 84752E7826ED0FFE009FD801 = { 189 | isa = PBXGroup; 190 | children = ( 191 | F87C2BBB27E68F330053D946 /* Extensions */, 192 | A91B2DEB276E1E5E001B0E95 /* README.md */, 193 | 84752EE926ED1402009FD801 /* Common */, 194 | 84752E8426ED0FFE009FD801 /* xDripClient */, 195 | 0AA1BFD427E030190098B986 /* xDripClientUI */, 196 | C187C198279086A8006E3557 /* xDripClientPlugin */, 197 | 84752E8326ED0FFE009FD801 /* Products */, 198 | 84752E9C26ED10A0009FD801 /* Frameworks */, 199 | 0AA1BFE827E0344A0098B986 /* Scripts */, 200 | ); 201 | sourceTree = ""; 202 | }; 203 | 84752E8326ED0FFE009FD801 /* Products */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 84752E8226ED0FFE009FD801 /* xDripClient.framework */, 207 | C187C197279086A8006E3557 /* xDripClientPlugin.loopplugin */, 208 | 0AA1BFD327E030190098B986 /* xDripClientUI.framework */, 209 | ); 210 | name = Products; 211 | sourceTree = ""; 212 | }; 213 | 84752E8426ED0FFE009FD801 /* xDripClient */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | F87C2BB727E675810053D946 /* BluetoothTransmitter.swift */, 217 | 0AA1BFF527E0BCE00098B986 /* xDripReading.swift */, 218 | 0AA1BFC727E02DC40098B986 /* xDripCGMManager.swift */, 219 | 0A53988227E0CCB700077BAF /* xDripAppGroup.swift */, 220 | 84752E8526ED0FFE009FD801 /* xDripClient.h */, 221 | 84752E8626ED0FFE009FD801 /* Info.plist */, 222 | ); 223 | path = xDripClient; 224 | sourceTree = ""; 225 | }; 226 | 84752E9C26ED10A0009FD801 /* Frameworks */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 0AA1BFCC27E02F530098B986 /* HealthKit.framework */, 230 | 847530F426ED65DD009FD801 /* LoopKit.framework */, 231 | 847530F526ED65DD009FD801 /* LoopKitUI.framework */, 232 | ); 233 | name = Frameworks; 234 | sourceTree = ""; 235 | }; 236 | 84752EE926ED1402009FD801 /* Common */ = { 237 | isa = PBXGroup; 238 | children = ( 239 | 0AA1BFED27E0354B0098B986 /* OSLog.swift */, 240 | 0AA1BFF727E0BD9B0098B986 /* HKUnit.swift */, 241 | 0A53987B27E0CA0100077BAF /* Framework.swift */, 242 | 0A53987E27E0CA2600077BAF /* UIImage.swift */, 243 | 0A53988427E0ECE100077BAF /* LocalizedString.swift */, 244 | F87C2BB927E68CAA0053D946 /* Trace.swift */, 245 | F8CB9E66282FE343008E8EA1 /* ConstantsxDripClient.swift */, 246 | ); 247 | path = Common; 248 | sourceTree = ""; 249 | }; 250 | C187C198279086A8006E3557 /* xDripClientPlugin */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | 0AA1BFC327E02D770098B986 /* xDripClientPlugin.h */, 254 | 0AA1BFC427E02D770098B986 /* xDripClientPlugin.swift */, 255 | C187C1A427908B1C006E3557 /* Info.plist */, 256 | ); 257 | path = xDripClientPlugin; 258 | sourceTree = ""; 259 | }; 260 | F87C2BBB27E68F330053D946 /* Extensions */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | F87C2BC027EA74260053D946 /* URL.swift */, 264 | F87C2BBE27E7DEE70053D946 /* CBManagerState.swift */, 265 | F87C2BBC27E68F9D0053D946 /* StringProtocol.swift */, 266 | ); 267 | path = Extensions; 268 | sourceTree = ""; 269 | }; 270 | /* End PBXGroup section */ 271 | 272 | /* Begin PBXHeadersBuildPhase section */ 273 | 0AA1BFCE27E030190098B986 /* Headers */ = { 274 | isa = PBXHeadersBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 0AA1BFE027E031060098B986 /* xDripClientUI.h in Headers */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | 84752E7D26ED0FFE009FD801 /* Headers */ = { 282 | isa = PBXHeadersBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | 84752E9326ED0FFE009FD801 /* xDripClient.h in Headers */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | C187C192279086A8006E3557 /* Headers */ = { 290 | isa = PBXHeadersBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 0AA1BFC927E02ECB0098B986 /* xDripClientPlugin.h in Headers */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | /* End PBXHeadersBuildPhase section */ 298 | 299 | /* Begin PBXNativeTarget section */ 300 | 0AA1BFD227E030190098B986 /* xDripClientUI */ = { 301 | isa = PBXNativeTarget; 302 | buildConfigurationList = 0AA1BFD727E0301A0098B986 /* Build configuration list for PBXNativeTarget "xDripClientUI" */; 303 | buildPhases = ( 304 | 0AA1BFCE27E030190098B986 /* Headers */, 305 | 0AA1BFCF27E030190098B986 /* Sources */, 306 | 0AA1BFD027E030190098B986 /* Frameworks */, 307 | 0AA1BFD127E030190098B986 /* Resources */, 308 | ); 309 | buildRules = ( 310 | ); 311 | dependencies = ( 312 | 0AA1BFDD27E0304B0098B986 /* PBXTargetDependency */, 313 | ); 314 | name = xDripClientUI; 315 | productName = xDripClientUI; 316 | productReference = 0AA1BFD327E030190098B986 /* xDripClientUI.framework */; 317 | productType = "com.apple.product-type.framework"; 318 | }; 319 | 84752E8126ED0FFE009FD801 /* xDripClient */ = { 320 | isa = PBXNativeTarget; 321 | buildConfigurationList = 84752E9626ED0FFE009FD801 /* Build configuration list for PBXNativeTarget "xDripClient" */; 322 | buildPhases = ( 323 | 84752E7D26ED0FFE009FD801 /* Headers */, 324 | 84752E7E26ED0FFE009FD801 /* Sources */, 325 | 84752E7F26ED0FFE009FD801 /* Frameworks */, 326 | 84752E8026ED0FFE009FD801 /* Resources */, 327 | ); 328 | buildRules = ( 329 | ); 330 | dependencies = ( 331 | ); 332 | name = xDripClient; 333 | packageProductDependencies = ( 334 | ); 335 | productName = OmniBLE; 336 | productReference = 84752E8226ED0FFE009FD801 /* xDripClient.framework */; 337 | productType = "com.apple.product-type.framework"; 338 | }; 339 | C187C196279086A8006E3557 /* xDripClientPlugin */ = { 340 | isa = PBXNativeTarget; 341 | buildConfigurationList = C187C19B279086A8006E3557 /* Build configuration list for PBXNativeTarget "xDripClientPlugin" */; 342 | buildPhases = ( 343 | C187C192279086A8006E3557 /* Headers */, 344 | 0A53987827E0C8DE00077BAF /* CopyFiles */, 345 | C187C193279086A8006E3557 /* Sources */, 346 | C187C194279086A8006E3557 /* Frameworks */, 347 | C187C195279086A8006E3557 /* Resources */, 348 | ); 349 | buildRules = ( 350 | ); 351 | dependencies = ( 352 | 0AA1BFF127E035DF0098B986 /* PBXTargetDependency */, 353 | C187C1A0279086FF006E3557 /* PBXTargetDependency */, 354 | ); 355 | name = xDripClientPlugin; 356 | productName = OmniBLEPlugin; 357 | productReference = C187C197279086A8006E3557 /* xDripClientPlugin.loopplugin */; 358 | productType = "com.apple.product-type.framework"; 359 | }; 360 | /* End PBXNativeTarget section */ 361 | 362 | /* Begin PBXProject section */ 363 | 84752E7926ED0FFE009FD801 /* Project object */ = { 364 | isa = PBXProject; 365 | attributes = { 366 | BuildIndependentTargetsInParallel = YES; 367 | LastSwiftUpdateCheck = 1250; 368 | LastUpgradeCheck = 1500; 369 | ORGANIZATIONNAME = "Randall Knutson"; 370 | TargetAttributes = { 371 | 0AA1BFD227E030190098B986 = { 372 | CreatedOnToolsVersion = 13.3; 373 | LastSwiftMigration = 1330; 374 | }; 375 | 84752E8126ED0FFE009FD801 = { 376 | CreatedOnToolsVersion = 12.5.1; 377 | LastSwiftMigration = 1330; 378 | }; 379 | C187C196279086A8006E3557 = { 380 | CreatedOnToolsVersion = 13.2.1; 381 | }; 382 | }; 383 | }; 384 | buildConfigurationList = 84752E7C26ED0FFE009FD801 /* Build configuration list for PBXProject "xDripClient" */; 385 | compatibilityVersion = "Xcode 9.3"; 386 | developmentRegion = en; 387 | hasScannedForEncodings = 0; 388 | knownRegions = ( 389 | en, 390 | Base, 391 | de, 392 | "zh-Hans", 393 | ja, 394 | nb, 395 | es, 396 | da, 397 | it, 398 | sv, 399 | pl, 400 | "pt-BR", 401 | vi, 402 | ru, 403 | fr, 404 | fi, 405 | nl, 406 | ro, 407 | ); 408 | mainGroup = 84752E7826ED0FFE009FD801; 409 | packageReferences = ( 410 | ); 411 | productRefGroup = 84752E8326ED0FFE009FD801 /* Products */; 412 | projectDirPath = ""; 413 | projectRoot = ""; 414 | targets = ( 415 | 84752E8126ED0FFE009FD801 /* xDripClient */, 416 | C187C196279086A8006E3557 /* xDripClientPlugin */, 417 | 0AA1BFD227E030190098B986 /* xDripClientUI */, 418 | ); 419 | }; 420 | /* End PBXProject section */ 421 | 422 | /* Begin PBXResourcesBuildPhase section */ 423 | 0AA1BFD127E030190098B986 /* Resources */ = { 424 | isa = PBXResourcesBuildPhase; 425 | buildActionMask = 2147483647; 426 | files = ( 427 | 0A53988127E0CA5200077BAF /* xDripClientUI.xcassets in Resources */, 428 | 0A53988927E0EFE100077BAF /* Localizable.strings in Resources */, 429 | ); 430 | runOnlyForDeploymentPostprocessing = 0; 431 | }; 432 | 84752E8026ED0FFE009FD801 /* Resources */ = { 433 | isa = PBXResourcesBuildPhase; 434 | buildActionMask = 2147483647; 435 | files = ( 436 | ); 437 | runOnlyForDeploymentPostprocessing = 0; 438 | }; 439 | C187C195279086A8006E3557 /* Resources */ = { 440 | isa = PBXResourcesBuildPhase; 441 | buildActionMask = 2147483647; 442 | files = ( 443 | ); 444 | runOnlyForDeploymentPostprocessing = 0; 445 | }; 446 | /* End PBXResourcesBuildPhase section */ 447 | 448 | /* Begin PBXSourcesBuildPhase section */ 449 | 0AA1BFCF27E030190098B986 /* Sources */ = { 450 | isa = PBXSourcesBuildPhase; 451 | buildActionMask = 2147483647; 452 | files = ( 453 | 0A53987D27E0CA0100077BAF /* Framework.swift in Sources */, 454 | 0A53987F27E0CA2600077BAF /* UIImage.swift in Sources */, 455 | 0AA1BFE427E032920098B986 /* UICoordinator.swift in Sources */, 456 | 0A53987527E0C10E00077BAF /* xDripStatusView.swift in Sources */, 457 | 0AA1BFE327E032920098B986 /* xDripCGMManager+UI.swift in Sources */, 458 | 0AA1BFF927E0BD9C0098B986 /* HKUnit.swift in Sources */, 459 | 0AA1BFEE27E0354B0098B986 /* OSLog.swift in Sources */, 460 | 0A53987727E0C13900077BAF /* xDripStatusModel.swift in Sources */, 461 | 0A53988627E0ECE200077BAF /* LocalizedString.swift in Sources */, 462 | F8CB9E65282FDB20008E8EA1 /* MailView.swift in Sources */, 463 | ); 464 | runOnlyForDeploymentPostprocessing = 0; 465 | }; 466 | 84752E7E26ED0FFE009FD801 /* Sources */ = { 467 | isa = PBXSourcesBuildPhase; 468 | buildActionMask = 2147483647; 469 | files = ( 470 | F87C2BB827E675810053D946 /* BluetoothTransmitter.swift in Sources */, 471 | 0AA1BFEF27E0357E0098B986 /* OSLog.swift in Sources */, 472 | 0AA1BFC827E02DC40098B986 /* xDripCGMManager.swift in Sources */, 473 | 0AA1BFF827E0BD9B0098B986 /* HKUnit.swift in Sources */, 474 | F87C2BBF27E7DEE70053D946 /* CBManagerState.swift in Sources */, 475 | F87C2BBD27E68F9D0053D946 /* StringProtocol.swift in Sources */, 476 | 0A53988527E0ECE100077BAF /* LocalizedString.swift in Sources */, 477 | F8CB9E67282FE343008E8EA1 /* ConstantsxDripClient.swift in Sources */, 478 | F87C2BBA27E68CAA0053D946 /* Trace.swift in Sources */, 479 | 0A53987C27E0CA0100077BAF /* Framework.swift in Sources */, 480 | 0AA1BFF627E0BCE00098B986 /* xDripReading.swift in Sources */, 481 | 0A53988327E0CCB700077BAF /* xDripAppGroup.swift in Sources */, 482 | F87C2BC127EA74260053D946 /* URL.swift in Sources */, 483 | ); 484 | runOnlyForDeploymentPostprocessing = 0; 485 | }; 486 | C187C193279086A8006E3557 /* Sources */ = { 487 | isa = PBXSourcesBuildPhase; 488 | buildActionMask = 2147483647; 489 | files = ( 490 | 0AA1BFF427E03ACE0098B986 /* OSLog.swift in Sources */, 491 | 0AA1BFC627E02D770098B986 /* xDripClientPlugin.swift in Sources */, 492 | ); 493 | runOnlyForDeploymentPostprocessing = 0; 494 | }; 495 | /* End PBXSourcesBuildPhase section */ 496 | 497 | /* Begin PBXTargetDependency section */ 498 | 0AA1BFDD27E0304B0098B986 /* PBXTargetDependency */ = { 499 | isa = PBXTargetDependency; 500 | target = 84752E8126ED0FFE009FD801 /* xDripClient */; 501 | targetProxy = 0AA1BFDC27E0304B0098B986 /* PBXContainerItemProxy */; 502 | }; 503 | 0AA1BFF127E035DF0098B986 /* PBXTargetDependency */ = { 504 | isa = PBXTargetDependency; 505 | target = 0AA1BFD227E030190098B986 /* xDripClientUI */; 506 | targetProxy = 0AA1BFF027E035DF0098B986 /* PBXContainerItemProxy */; 507 | }; 508 | C187C1A0279086FF006E3557 /* PBXTargetDependency */ = { 509 | isa = PBXTargetDependency; 510 | target = 84752E8126ED0FFE009FD801 /* xDripClient */; 511 | targetProxy = C187C19F279086FF006E3557 /* PBXContainerItemProxy */; 512 | }; 513 | /* End PBXTargetDependency section */ 514 | 515 | /* Begin PBXVariantGroup section */ 516 | 0A53988727E0EFE100077BAF /* Localizable.strings */ = { 517 | isa = PBXVariantGroup; 518 | children = ( 519 | 0A53988827E0EFE100077BAF /* Base */, 520 | 0A5398AE27E0F03600077BAF /* da */, 521 | 0A5398AF27E0F04A00077BAF /* de */, 522 | 0A5398B027E0F05A00077BAF /* zh-Hans */, 523 | 0A5398B327E0F0B900077BAF /* nl */, 524 | ); 525 | name = Localizable.strings; 526 | sourceTree = ""; 527 | }; 528 | /* End PBXVariantGroup section */ 529 | 530 | /* Begin XCBuildConfiguration section */ 531 | 0AA1BFD827E0301A0098B986 /* Debug */ = { 532 | isa = XCBuildConfiguration; 533 | buildSettings = { 534 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 535 | CLANG_ENABLE_MODULES = YES; 536 | CODE_SIGN_IDENTITY = ""; 537 | CODE_SIGN_STYLE = Automatic; 538 | CURRENT_PROJECT_VERSION = 1; 539 | DEFINES_MODULE = YES; 540 | DEVELOPMENT_TEAM = ""; 541 | DYLIB_COMPATIBILITY_VERSION = 1; 542 | DYLIB_CURRENT_VERSION = 1; 543 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 544 | ENABLE_MODULE_VERIFIER = YES; 545 | GENERATE_INFOPLIST_FILE = NO; 546 | INFOPLIST_FILE = xDripClientUI/info.plist; 547 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Julian Groen. All rights reserved."; 548 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 549 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 550 | LD_RUNPATH_SEARCH_PATHS = ( 551 | "$(inherited)", 552 | "@executable_path/Frameworks", 553 | "@loader_path/Frameworks", 554 | ); 555 | MARKETING_VERSION = 1.0; 556 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 557 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; 558 | PRODUCT_BUNDLE_IDENTIFIER = com.juliangroen.xDripClientUI; 559 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 560 | SKIP_INSTALL = YES; 561 | SUPPORTS_MACCATALYST = YES; 562 | SWIFT_EMIT_LOC_STRINGS = YES; 563 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 564 | SWIFT_VERSION = 5.0; 565 | TARGETED_DEVICE_FAMILY = "1,2"; 566 | }; 567 | name = Debug; 568 | }; 569 | 0AA1BFD927E0301A0098B986 /* Release */ = { 570 | isa = XCBuildConfiguration; 571 | buildSettings = { 572 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 573 | CLANG_ENABLE_MODULES = YES; 574 | CODE_SIGN_IDENTITY = ""; 575 | CODE_SIGN_STYLE = Automatic; 576 | CURRENT_PROJECT_VERSION = 1; 577 | DEFINES_MODULE = YES; 578 | DEVELOPMENT_TEAM = ""; 579 | DYLIB_COMPATIBILITY_VERSION = 1; 580 | DYLIB_CURRENT_VERSION = 1; 581 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 582 | ENABLE_MODULE_VERIFIER = YES; 583 | GENERATE_INFOPLIST_FILE = NO; 584 | INFOPLIST_FILE = xDripClientUI/info.plist; 585 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Julian Groen. All rights reserved."; 586 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 587 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 588 | LD_RUNPATH_SEARCH_PATHS = ( 589 | "$(inherited)", 590 | "@executable_path/Frameworks", 591 | "@loader_path/Frameworks", 592 | ); 593 | MARKETING_VERSION = 1.0; 594 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 595 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; 596 | PRODUCT_BUNDLE_IDENTIFIER = com.juliangroen.xDripClientUI; 597 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 598 | SKIP_INSTALL = YES; 599 | SUPPORTS_MACCATALYST = YES; 600 | SWIFT_EMIT_LOC_STRINGS = YES; 601 | SWIFT_VERSION = 5.0; 602 | TARGETED_DEVICE_FAMILY = "1,2"; 603 | }; 604 | name = Release; 605 | }; 606 | 84752E9426ED0FFE009FD801 /* Debug */ = { 607 | isa = XCBuildConfiguration; 608 | buildSettings = { 609 | ALWAYS_SEARCH_USER_PATHS = NO; 610 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 611 | CLANG_ANALYZER_NONNULL = YES; 612 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 613 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 614 | CLANG_CXX_LIBRARY = "libc++"; 615 | CLANG_ENABLE_MODULES = YES; 616 | CLANG_ENABLE_OBJC_ARC = YES; 617 | CLANG_ENABLE_OBJC_WEAK = YES; 618 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 619 | CLANG_WARN_BOOL_CONVERSION = YES; 620 | CLANG_WARN_COMMA = YES; 621 | CLANG_WARN_CONSTANT_CONVERSION = YES; 622 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 623 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 624 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 625 | CLANG_WARN_EMPTY_BODY = YES; 626 | CLANG_WARN_ENUM_CONVERSION = YES; 627 | CLANG_WARN_INFINITE_RECURSION = YES; 628 | CLANG_WARN_INT_CONVERSION = YES; 629 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 630 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 631 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 632 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 633 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 634 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 635 | CLANG_WARN_STRICT_PROTOTYPES = YES; 636 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 637 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 638 | CLANG_WARN_UNREACHABLE_CODE = YES; 639 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 640 | COPY_PHASE_STRIP = NO; 641 | CURRENT_PROJECT_VERSION = 1; 642 | DEBUG_INFORMATION_FORMAT = dwarf; 643 | ENABLE_STRICT_OBJC_MSGSEND = YES; 644 | ENABLE_TESTABILITY = YES; 645 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 646 | GCC_C_LANGUAGE_STANDARD = gnu11; 647 | GCC_DYNAMIC_NO_PIC = NO; 648 | GCC_NO_COMMON_BLOCKS = YES; 649 | GCC_OPTIMIZATION_LEVEL = 0; 650 | GCC_PREPROCESSOR_DEFINITIONS = ( 651 | "DEBUG=1", 652 | "$(inherited)", 653 | ); 654 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 655 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 656 | GCC_WARN_UNDECLARED_SELECTOR = YES; 657 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 658 | GCC_WARN_UNUSED_FUNCTION = YES; 659 | GCC_WARN_UNUSED_VARIABLE = YES; 660 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 661 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 662 | MTL_FAST_MATH = YES; 663 | ONLY_ACTIVE_ARCH = YES; 664 | SDKROOT = iphoneos; 665 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 666 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 667 | VERSIONING_SYSTEM = "apple-generic"; 668 | VERSION_INFO_PREFIX = ""; 669 | }; 670 | name = Debug; 671 | }; 672 | 84752E9526ED0FFE009FD801 /* Release */ = { 673 | isa = XCBuildConfiguration; 674 | buildSettings = { 675 | ALWAYS_SEARCH_USER_PATHS = NO; 676 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 677 | CLANG_ANALYZER_NONNULL = YES; 678 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 679 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 680 | CLANG_CXX_LIBRARY = "libc++"; 681 | CLANG_ENABLE_MODULES = YES; 682 | CLANG_ENABLE_OBJC_ARC = YES; 683 | CLANG_ENABLE_OBJC_WEAK = YES; 684 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 685 | CLANG_WARN_BOOL_CONVERSION = YES; 686 | CLANG_WARN_COMMA = YES; 687 | CLANG_WARN_CONSTANT_CONVERSION = YES; 688 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 689 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 690 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 691 | CLANG_WARN_EMPTY_BODY = YES; 692 | CLANG_WARN_ENUM_CONVERSION = YES; 693 | CLANG_WARN_INFINITE_RECURSION = YES; 694 | CLANG_WARN_INT_CONVERSION = YES; 695 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 696 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 697 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 698 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 699 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 700 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 701 | CLANG_WARN_STRICT_PROTOTYPES = YES; 702 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 703 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 704 | CLANG_WARN_UNREACHABLE_CODE = YES; 705 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 706 | COPY_PHASE_STRIP = NO; 707 | CURRENT_PROJECT_VERSION = 1; 708 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 709 | ENABLE_NS_ASSERTIONS = NO; 710 | ENABLE_STRICT_OBJC_MSGSEND = YES; 711 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 712 | GCC_C_LANGUAGE_STANDARD = gnu11; 713 | GCC_NO_COMMON_BLOCKS = YES; 714 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 715 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 716 | GCC_WARN_UNDECLARED_SELECTOR = YES; 717 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 718 | GCC_WARN_UNUSED_FUNCTION = YES; 719 | GCC_WARN_UNUSED_VARIABLE = YES; 720 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 721 | MTL_ENABLE_DEBUG_INFO = NO; 722 | MTL_FAST_MATH = YES; 723 | SDKROOT = iphoneos; 724 | SWIFT_COMPILATION_MODE = wholemodule; 725 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 726 | VALIDATE_PRODUCT = YES; 727 | VERSIONING_SYSTEM = "apple-generic"; 728 | VERSION_INFO_PREFIX = ""; 729 | }; 730 | name = Release; 731 | }; 732 | 84752E9726ED0FFE009FD801 /* Debug */ = { 733 | isa = XCBuildConfiguration; 734 | buildSettings = { 735 | CLANG_ENABLE_MODULES = YES; 736 | CODE_SIGN_IDENTITY = ""; 737 | CODE_SIGN_STYLE = Automatic; 738 | DEFINES_MODULE = YES; 739 | DYLIB_COMPATIBILITY_VERSION = 1; 740 | DYLIB_CURRENT_VERSION = 1; 741 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 742 | ENABLE_MODULE_VERIFIER = YES; 743 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS"; 744 | INFOPLIST_FILE = xDripClient/Info.plist; 745 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Julian Groen. All rights reserved."; 746 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 747 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 748 | LD_RUNPATH_SEARCH_PATHS = ( 749 | "$(inherited)", 750 | "@executable_path/Frameworks", 751 | "@loader_path/Frameworks", 752 | ); 753 | MARKETING_VERSION = 1.0; 754 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 755 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; 756 | PRODUCT_BUNDLE_IDENTIFIER = com.juliangroen.xDripClient; 757 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 758 | SKIP_INSTALL = YES; 759 | SUPPORTS_MACCATALYST = YES; 760 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 761 | SWIFT_VERSION = 5.0; 762 | TARGETED_DEVICE_FAMILY = "1,2"; 763 | WRAPPER_EXTENSION = framework; 764 | }; 765 | name = Debug; 766 | }; 767 | 84752E9826ED0FFE009FD801 /* Release */ = { 768 | isa = XCBuildConfiguration; 769 | buildSettings = { 770 | CLANG_ENABLE_MODULES = YES; 771 | CODE_SIGN_IDENTITY = ""; 772 | CODE_SIGN_STYLE = Automatic; 773 | DEFINES_MODULE = YES; 774 | DYLIB_COMPATIBILITY_VERSION = 1; 775 | DYLIB_CURRENT_VERSION = 1; 776 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 777 | ENABLE_MODULE_VERIFIER = YES; 778 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS"; 779 | INFOPLIST_FILE = xDripClient/Info.plist; 780 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Julian Groen. All rights reserved."; 781 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 782 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 783 | LD_RUNPATH_SEARCH_PATHS = ( 784 | "$(inherited)", 785 | "@executable_path/Frameworks", 786 | "@loader_path/Frameworks", 787 | ); 788 | MARKETING_VERSION = 1.0; 789 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 790 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; 791 | PRODUCT_BUNDLE_IDENTIFIER = com.juliangroen.xDripClient; 792 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 793 | SKIP_INSTALL = YES; 794 | SUPPORTS_MACCATALYST = YES; 795 | SWIFT_VERSION = 5.0; 796 | TARGETED_DEVICE_FAMILY = "1,2"; 797 | WRAPPER_EXTENSION = framework; 798 | }; 799 | name = Release; 800 | }; 801 | C187C19C279086A8006E3557 /* Debug */ = { 802 | isa = XCBuildConfiguration; 803 | buildSettings = { 804 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 805 | CODE_SIGN_IDENTITY = ""; 806 | CODE_SIGN_STYLE = Automatic; 807 | CURRENT_PROJECT_VERSION = 1; 808 | DEFINES_MODULE = NO; 809 | DEVELOPMENT_TEAM = ""; 810 | DYLIB_COMPATIBILITY_VERSION = 1; 811 | DYLIB_CURRENT_VERSION = 1; 812 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 813 | GENERATE_INFOPLIST_FILE = NO; 814 | INFOPLIST_FILE = xDripClientPlugin/Info.plist; 815 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Julian Groen. All rights reserved."; 816 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 817 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 818 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 15.1; 819 | LD_DYLIB_INSTALL_NAME = ""; 820 | LD_RUNPATH_SEARCH_PATHS = ( 821 | "$(inherited)", 822 | "@executable_path/Frameworks", 823 | "@loader_path/Frameworks", 824 | ); 825 | MARKETING_VERSION = 1.0; 826 | PRODUCT_BUNDLE_IDENTIFIER = com.juliangroen.xDripClientPlugin; 827 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 828 | SKIP_INSTALL = YES; 829 | SUPPORTS_MACCATALYST = YES; 830 | SWIFT_EMIT_LOC_STRINGS = YES; 831 | SWIFT_VERSION = 5.0; 832 | TARGETED_DEVICE_FAMILY = "1,2"; 833 | WRAPPER_EXTENSION = loopplugin; 834 | }; 835 | name = Debug; 836 | }; 837 | C187C19D279086A8006E3557 /* Release */ = { 838 | isa = XCBuildConfiguration; 839 | buildSettings = { 840 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 841 | CODE_SIGN_IDENTITY = ""; 842 | CODE_SIGN_STYLE = Automatic; 843 | CURRENT_PROJECT_VERSION = 1; 844 | DEFINES_MODULE = NO; 845 | DEVELOPMENT_TEAM = ""; 846 | DYLIB_COMPATIBILITY_VERSION = 1; 847 | DYLIB_CURRENT_VERSION = 1; 848 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 849 | GENERATE_INFOPLIST_FILE = NO; 850 | INFOPLIST_FILE = xDripClientPlugin/Info.plist; 851 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Julian Groen. All rights reserved."; 852 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 853 | IPHONEOS_DEPLOYMENT_TARGET = 15.1; 854 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 15.1; 855 | LD_DYLIB_INSTALL_NAME = ""; 856 | LD_RUNPATH_SEARCH_PATHS = ( 857 | "$(inherited)", 858 | "@executable_path/Frameworks", 859 | "@loader_path/Frameworks", 860 | ); 861 | MARKETING_VERSION = 1.0; 862 | PRODUCT_BUNDLE_IDENTIFIER = com.juliangroen.xDripClientPlugin; 863 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 864 | SKIP_INSTALL = YES; 865 | SUPPORTS_MACCATALYST = YES; 866 | SWIFT_EMIT_LOC_STRINGS = YES; 867 | SWIFT_VERSION = 5.0; 868 | TARGETED_DEVICE_FAMILY = "1,2"; 869 | WRAPPER_EXTENSION = loopplugin; 870 | }; 871 | name = Release; 872 | }; 873 | /* End XCBuildConfiguration section */ 874 | 875 | /* Begin XCConfigurationList section */ 876 | 0AA1BFD727E0301A0098B986 /* Build configuration list for PBXNativeTarget "xDripClientUI" */ = { 877 | isa = XCConfigurationList; 878 | buildConfigurations = ( 879 | 0AA1BFD827E0301A0098B986 /* Debug */, 880 | 0AA1BFD927E0301A0098B986 /* Release */, 881 | ); 882 | defaultConfigurationIsVisible = 0; 883 | defaultConfigurationName = Release; 884 | }; 885 | 84752E7C26ED0FFE009FD801 /* Build configuration list for PBXProject "xDripClient" */ = { 886 | isa = XCConfigurationList; 887 | buildConfigurations = ( 888 | 84752E9426ED0FFE009FD801 /* Debug */, 889 | 84752E9526ED0FFE009FD801 /* Release */, 890 | ); 891 | defaultConfigurationIsVisible = 0; 892 | defaultConfigurationName = Release; 893 | }; 894 | 84752E9626ED0FFE009FD801 /* Build configuration list for PBXNativeTarget "xDripClient" */ = { 895 | isa = XCConfigurationList; 896 | buildConfigurations = ( 897 | 84752E9726ED0FFE009FD801 /* Debug */, 898 | 84752E9826ED0FFE009FD801 /* Release */, 899 | ); 900 | defaultConfigurationIsVisible = 0; 901 | defaultConfigurationName = Release; 902 | }; 903 | C187C19B279086A8006E3557 /* Build configuration list for PBXNativeTarget "xDripClientPlugin" */ = { 904 | isa = XCConfigurationList; 905 | buildConfigurations = ( 906 | C187C19C279086A8006E3557 /* Debug */, 907 | C187C19D279086A8006E3557 /* Release */, 908 | ); 909 | defaultConfigurationIsVisible = 0; 910 | defaultConfigurationName = Release; 911 | }; 912 | /* End XCConfigurationList section */ 913 | }; 914 | rootObject = 84752E7926ED0FFE009FD801 /* Project object */; 915 | } 916 | --------------------------------------------------------------------------------