Skip to content

Commit

Permalink
Plugins (LoopKit#22)
Browse files Browse the repository at this point in the history
* Add DashKit

* Remove output from copy-files phase to force update each time

* Sign plugins

* Remove quotes to handle empty list

* Handle spaces, deref symlink, and re-enable bitcode

* Comment out testing vars

* Fix version for archive builds

* Move plugin's frameworks to main app frameworks directory

* Fix search for plugin's frameworks

* Set marketing version via config

* Update copy frameworks script to dereference framework symlinks

* Use copy-frameworks script for extensions and learn app

* Bumping build version

* Add missing script

* Back out development team id changes

* Scripts fixes from review

* Fix capitalization

* Small tweaks
  • Loading branch information
ps2 authored Aug 23, 2019
1 parent 406c6f9 commit 615f06a
Show file tree
Hide file tree
Showing 118 changed files with 417 additions and 135 deletions.
28 changes: 10 additions & 18 deletions Common/Models/PumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,23 @@ import MinimedKit
import MockKit


let allPumpManagers: [PumpManager.Type] = [
public struct AvailableDevice {
let identifier: String
let localizedTitle: String
}


let staticPumpManagers: [PumpManager.Type] = [
MinimedPumpManager.self,
MockPumpManager.self,
]

private let managersByIdentifier: [String: PumpManager.Type] = allPumpManagers.reduce(into: [:]) { (map, Type) in
let staticPumpManagersByIdentifier: [String: PumpManager.Type] = staticPumpManagers.reduce(into: [:]) { (map, Type) in
map[Type.managerIdentifier] = Type
}

func PumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? {
guard let managerIdentifier = rawValue["managerIdentifier"] as? String else {
return nil
}

return managersByIdentifier[managerIdentifier]
}

func PumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManager? {
guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
let Manager = PumpManagerTypeFromRawValue(rawValue)
else {
return nil
}

return Manager.init(rawState: rawState)
let availableStaticPumpManagers = staticPumpManagers.map { (Type) -> AvailableDevice in
return AvailableDevice(identifier: Type.managerIdentifier, localizedTitle: Type.localizedTitle)
}

extension PumpManager {
Expand Down
2 changes: 1 addition & 1 deletion Common/Models/PumpManagerUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import LoopKitUI
import MinimedKit
import MinimedKitUI

private let managersByIdentifier: [String: PumpManagerUI.Type] = allPumpManagers.compactMap{ $0 as? PumpManagerUI.Type}.reduce(into: [:]) { (map, Type) in
private let managersByIdentifier: [String: PumpManagerUI.Type] = staticPumpManagers.compactMap{ $0 as? PumpManagerUI.Type}.reduce(into: [:]) { (map, Type) in
map[Type.managerIdentifier] = Type
}

Expand Down
2 changes: 1 addition & 1 deletion DoseMathTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.10.0dev</string>
<string>1.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
2 changes: 1 addition & 1 deletion Learn/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.10.0dev</string>
<string>1.10.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
Expand Down
3 changes: 0 additions & 3 deletions Loop Status Extension/Base.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/* (No Comment) */
"CFBundleDisplayName" = "Loop";

/* (No Comment) */
"CFBundleName" = "$(PRODUCT_NAME)";

10 changes: 5 additions & 5 deletions Loop Status Extension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppGroupIdentifier</key>
<string>$(APP_GROUP_IDENTIFIER)</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>MainAppBundleIdentifier</key>
<string>$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
<key>CFBundleDisplayName</key>
<string>Loop</string>
<key>CFBundleExecutable</key>
Expand All @@ -19,11 +19,11 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.10.0dev</string>
<string>$(LOOP_MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>AppGroupIdentifier</key>
<string>$(APP_GROUP_IDENTIFIER)</string>
<key>MainAppBundleIdentifier</key>
<string>$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>
Expand Down
5 changes: 2 additions & 3 deletions Loop.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
MAIN_APP_BUNDLE_IDENTIFIER = com.${DEVELOPMENT_TEAM}.loopkit
MAIN_APP_DISPLAY_NAME = Loop

APPICON_NAME = AppIcon
LOOP_MARKETING_VERSION = 1.10.0

// Exclude additional assets by default, and let other configurations that provide them add the file back in
EXCLUDED_SOURCE_FILE_NAMES = AdditionalAssets.xcassets
APPICON_NAME = AppIcon

// Optional workspace configuration overrides
#include? "../LoopConfigOverride.xcconfig"
Expand Down
187 changes: 124 additions & 63 deletions Loop.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions Loop/Base.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* Bundle display name */
"CFBundleDisplayName" = "Loop";

/* Bundle name */
"CFBundleName" = "$(PRODUCT_NAME)";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "App Store Copy@2x.png",
"filename" : "AppStore-1024pt@1x.png",
"scale" : "1x"
}
],
Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes
8 changes: 4 additions & 4 deletions Loop/Extensions/UIAlertController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ extension UIAlertController {
/// - cgmManagers: An array of PumpManagers
/// - selectionHandler: A closure to execute when a manager is selected
/// - manager: The selected manager
internal convenience init(pumpManagers: [PumpManagerUI.Type], selectionHandler: @escaping (_ manager: PumpManagerUI.Type) -> Void) {
internal convenience init(pumpManagers: [AvailableDevice], selectionHandler: @escaping (_ identifier: String) -> Void) {
self.init(
title: NSLocalizedString("Add Pump", comment: "Action sheet title selecting Pump"),
message: nil,
preferredStyle: .actionSheet
)

for manager in pumpManagers {
for device in pumpManagers {
addAction(UIAlertAction(
title: manager.localizedTitle,
title: device.localizedTitle,
style: .default,
handler: { (_) in
selectionHandler(manager)
selectionHandler(device.identifier)
}
))
}
Expand Down
18 changes: 3 additions & 15 deletions Loop/Extensions/UserDefaults+Loop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,15 @@ extension UserDefaults {
case pumpManagerState = "com.loopkit.Loop.PumpManagerState"
}

var pumpManager: PumpManager? {
var pumpManagerRawValue: [String: Any]? {
get {
guard let rawValue = dictionary(forKey: Key.pumpManagerState.rawValue) else {
return nil
}

return PumpManagerFromRawValue(rawValue)
return dictionary(forKey: Key.pumpManagerState.rawValue)
}
set {
set(newValue?.rawValue, forKey: Key.pumpManagerState.rawValue)
set(newValue, forKey: Key.pumpManagerState.rawValue)
}
}

var isCGMManagerValidPumpManager: Bool {
guard let rawValue = cgmManagerState else {
return false
}

return PumpManagerTypeFromRawValue(rawValue) != nil
}

var cgmManager: CGMManager? {
get {
guard let rawValue = cgmManagerState else {
Expand Down
2 changes: 1 addition & 1 deletion Loop/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.10.0dev</string>
<string>$(LOOP_MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
Expand Down
54 changes: 50 additions & 4 deletions Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ final class DeviceDataManager {

NotificationCenter.default.post(name: .PumpManagerChanged, object: self, userInfo: nil)

UserDefaults.appGroup?.pumpManager = pumpManager
UserDefaults.appGroup?.pumpManagerRawValue = pumpManager?.rawValue
}
}

Expand All @@ -72,16 +72,27 @@ final class DeviceDataManager {

private var statusExtensionManager: StatusExtensionDataManager!

// MARK: - Plugins

private var pluginManager: PluginManager

// MARK: - Initialization


private(set) var loopManager: LoopDataManager!

init() {
pumpManager = UserDefaults.appGroup?.pumpManager as? PumpManagerUI
pluginManager = PluginManager()

if let pumpManagerRawValue = UserDefaults.appGroup?.pumpManagerRawValue {
pumpManager = pumpManagerFromRawValue(pumpManagerRawValue)
} else {
pumpManager = nil
}

if let cgmManager = UserDefaults.appGroup?.cgmManager {
self.cgmManager = cgmManager
} else if UserDefaults.appGroup?.isCGMManagerValidPumpManager == true {
} else if isCGMManagerValidPumpManager {
self.cgmManager = pumpManager as? CGMManager
}

Expand All @@ -102,6 +113,41 @@ final class DeviceDataManager {
setupPump()
setupCGM()
}

var isCGMManagerValidPumpManager: Bool {
guard let rawValue = UserDefaults.appGroup?.cgmManagerState else {
return false
}

return pumpManagerTypeFromRawValue(rawValue) != nil
}

var availablePumpManagers: [AvailableDevice] {
return pluginManager.availablePumpManagers + availableStaticPumpManagers
}

public func pumpManagerTypeByIdentifier(_ identifier: String) -> PumpManagerUI.Type? {
return pluginManager.getPumpManagerTypeByIdentifier(identifier) ?? staticPumpManagersByIdentifier[identifier] as? PumpManagerUI.Type
}

private func pumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? {
guard let managerIdentifier = rawValue["managerIdentifier"] as? String else {
return nil
}

return pumpManagerTypeByIdentifier(managerIdentifier)
}

func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? {
guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
let Manager = pumpManagerTypeFromRawValue(rawValue)
else {
return nil
}

return Manager.init(rawState: rawState) as? PumpManagerUI
}

}

private extension DeviceDataManager {
Expand Down Expand Up @@ -267,7 +313,7 @@ extension DeviceDataManager: PumpManagerDelegate {
dispatchPrecondition(condition: .onQueue(queue))
log.default("PumpManager:\(type(of: pumpManager)) did update state")

UserDefaults.appGroup?.pumpManager = pumpManager
UserDefaults.appGroup?.pumpManagerRawValue = pumpManager.rawValue
}

func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager) {
Expand Down
79 changes: 79 additions & 0 deletions Loop/Plugins/LoopPlugins.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// LoopPlugins.swift
// Loop
//
// Created by Pete Schwamb on 7/24/19.
// Copyright © 2019 LoopKit Authors. All rights reserved.
//

import Foundation
import LoopKit
import LoopKitUI

class PluginManager {
private let pluginBundles: [Bundle]

public init() {
var bundles = [Bundle]()

if let pluginsURL = Bundle.main.privateFrameworksURL {
do {
for pluginURL in try FileManager.default.contentsOfDirectory(at: pluginsURL, includingPropertiesForKeys: nil).filter{$0.path.hasSuffix(".framework")} {
if let bundle = Bundle(url: pluginURL), bundle.isLoopPlugin {
print("Found loop plugin at \(pluginURL)")
bundles.append(bundle)
}
}
} catch let error {
print("Error loading plugins: \(String(describing: error))")
}
}
self.pluginBundles = bundles
}

func getPumpManagerTypeByIdentifier(_ identifier: String) -> PumpManagerUI.Type? {
for bundle in pluginBundles {
if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String, name == identifier {
do {
try bundle.loadAndReturnError()

if let principalClass = bundle.principalClass as? NSObject.Type {

if let plugin = principalClass.init() as? LoopUIPlugin {
return plugin.pumpManagerType
} else {
fatalError("PrincipalClass does not conform to LoopUIPlugin")
}

} else {
fatalError("PrincipalClass not found")
}
} catch let error {
print(error)
}
}
}
return nil
}

var availablePumpManagers: [AvailableDevice] {
return pluginBundles.compactMap({ (bundle) -> AvailableDevice? in
guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerDisplayName.rawValue) as? String,
let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String else {
return nil
}

return AvailableDevice(identifier: identifier, localizedTitle: title)

})
}
}


extension Bundle {
var isLoopPlugin: Bool {
return
object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String != nil ||
object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerIdentifier.rawValue) as? String != nil
}
}
9 changes: 5 additions & 4 deletions Loop/View Controllers/SettingsTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -382,17 +382,18 @@ final class SettingsTableViewController: UITableViewController {
present(settings, animated: true)
} else {
// Add new pump
let pumpManagers = allPumpManagers.compactMap({ $0 as? PumpManagerUI.Type })
let pumpManagers = dataManager.availablePumpManagers

switch pumpManagers.count {
case 1:
if let PumpManagerType = pumpManagers.first {
if let pumpManager = pumpManagers.first, let PumpManagerType = dataManager.pumpManagerTypeByIdentifier(pumpManager.identifier) {

let setupViewController = configuredSetupViewController(for: PumpManagerType)
present(setupViewController, animated: true, completion: nil)
}
case let x where x > 1:
let alert = UIAlertController(pumpManagers: pumpManagers) { [weak self] (manager) in
if let self = self {
let alert = UIAlertController(pumpManagers: pumpManagers) { [weak self] (identifier) in
if let self = self, let manager = self.dataManager.pumpManagerTypeByIdentifier(identifier) {
let setupViewController = self.configuredSetupViewController(for: manager)
self.present(setupViewController, animated: true, completion: nil)
}
Expand Down
2 changes: 1 addition & 1 deletion LoopCore/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.10.0dev</string>
<string>$(LOOP_MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
Expand Down
Loading

0 comments on commit 615f06a

Please sign in to comment.