diff --git a/.build/dependencies-state.json b/.build/dependencies-state.json
new file mode 100644
index 0000000..9625dc8
--- /dev/null
+++ b/.build/dependencies-state.json
@@ -0,0 +1 @@
+{"object": {"dependencies": []}, "version": 2}
\ No newline at end of file
diff --git a/.build/regenerate-token b/.build/regenerate-token
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/.build/regenerate-token
@@ -0,0 +1 @@
+0
diff --git a/.build/repositories/checkouts-state.json b/.build/repositories/checkouts-state.json
new file mode 100644
index 0000000..e95fc40
--- /dev/null
+++ b/.build/repositories/checkouts-state.json
@@ -0,0 +1 @@
+{"object": {"repositories": {}}, "version": 1}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 65105cd..5c10b4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,15 +47,13 @@ playground.xcworkspace
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
+#
+# Add this line if you want to avoid checking in source code from the Xcode workspace
+# *.xcworkspace
# Carthage
-#
Carthage/*
-# Rswift
-#
-*.generated.swift
-
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
@@ -67,3 +65,7 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
+
+# SPM-generated Xcode projects
+Android/app/src/main/swift/BluetoothExplorer.xcodeproj/*
+BluetoothExplorer.xcodeproj/*
\ No newline at end of file
diff --git a/Android/.gitignore b/Android/.gitignore
new file mode 100644
index 0000000..70acd67
--- /dev/null
+++ b/Android/.gitignore
@@ -0,0 +1,7 @@
+*.iml
+.gradle
+/local.properties
+/.idea
+/build
+/captures
+.externalNativeBuild
diff --git a/Android/app/.gitignore b/Android/app/.gitignore
new file mode 100644
index 0000000..81cda7b
--- /dev/null
+++ b/Android/app/.gitignore
@@ -0,0 +1,4 @@
+/build
+/src/main/jniLibs
+/src/main/swift/.build
+/src/main/swift/Package.resolved
\ No newline at end of file
diff --git a/Android/app/build.gradle b/Android/app/build.gradle
new file mode 100644
index 0000000..24a23f1
--- /dev/null
+++ b/Android/app/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'net.zhuoweizhang.swiftandroid'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.millertech.bluetoothexplorer"
+ minSdkVersion 21
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+ implementation 'com.github.PureSwift:SwiftAndroidSupport:v0.2.1'
+
+ //implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
diff --git a/Android/app/proguard-rules.pro b/Android/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/Android/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/Android/app/src/androidTest/java/com/millertech/bluetoothexplorer/ExampleInstrumentedTest.kt b/Android/app/src/androidTest/java/com/millertech/bluetoothexplorer/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..9f8b95d
--- /dev/null
+++ b/Android/app/src/androidTest/java/com/millertech/bluetoothexplorer/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.millertech.bluetoothexplorer
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.runner.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getTargetContext()
+ assertEquals("com.millertech.bluetoothexplorer", appContext.packageName)
+ }
+}
diff --git a/Android/app/src/main/AndroidManifest.xml b/Android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9911462
--- /dev/null
+++ b/Android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/java/com/millertech/bluetoothexplorer/Application.kt b/Android/app/src/main/java/com/millertech/bluetoothexplorer/Application.kt
new file mode 100644
index 0000000..ea86e6a
--- /dev/null
+++ b/Android/app/src/main/java/com/millertech/bluetoothexplorer/Application.kt
@@ -0,0 +1,13 @@
+package com.millertech.bluetoothexplorer
+
+import org.pureswift.swiftandroidsupport.app.SwiftApplication
+
+class Application: SwiftApplication() {
+
+ companion object {
+
+ init {
+ System.loadLibrary("BluetoothExplorer")
+ }
+ }
+}
\ No newline at end of file
diff --git a/Android/app/src/main/java/com/millertech/bluetoothexplorer/MainActivity.kt b/Android/app/src/main/java/com/millertech/bluetoothexplorer/MainActivity.kt
new file mode 100644
index 0000000..0664887
--- /dev/null
+++ b/Android/app/src/main/java/com/millertech/bluetoothexplorer/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.millertech.bluetoothexplorer
+
+import org.pureswift.swiftandroidsupport.app.SwiftAppCompatActivity
+
+class MainActivity : SwiftAppCompatActivity()
diff --git a/Android/app/src/main/java/com/millertech/bluetoothexplorer/swiftbindings/MainActivityBindings.java b/Android/app/src/main/java/com/millertech/bluetoothexplorer/swiftbindings/MainActivityBindings.java
new file mode 100644
index 0000000..ceec0ee
--- /dev/null
+++ b/Android/app/src/main/java/com/millertech/bluetoothexplorer/swiftbindings/MainActivityBindings.java
@@ -0,0 +1,16 @@
+package com.millertech.bluetoothexplorer.swiftbindings;
+
+public interface MainActivityBindings {
+
+ // Messages from Java to Swift
+ interface Listener {
+
+ void hi();
+
+ }
+
+ // Messages from Swift back to Java Activity
+ interface Responder{
+
+ }
+}
diff --git a/Android/app/src/main/java/org/swiftjava/com_millertech/MainActivityBindings_ListenerProxy.java b/Android/app/src/main/java/org/swiftjava/com_millertech/MainActivityBindings_ListenerProxy.java
new file mode 100644
index 0000000..5392ae8
--- /dev/null
+++ b/Android/app/src/main/java/org/swiftjava/com_millertech/MainActivityBindings_ListenerProxy.java
@@ -0,0 +1,32 @@
+
+/// generated by: genswift.java 'java/lang|java/util|java/sql' 'Sources' '../java' ///
+
+/// interface com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings$Listener ///
+
+package org.swiftjava.com_millertech;
+
+@SuppressWarnings("JniMissingFunction")
+public class MainActivityBindings_ListenerProxy implements com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings.Listener {
+
+ // address of proxy object
+ long __swiftObject;
+
+ MainActivityBindings_ListenerProxy( long __swiftObject ) {
+ this.__swiftObject = __swiftObject;
+ }
+
+ /// public abstract void com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings$Listener.hi()
+
+ public native void __hi( long __swiftObject );
+
+ public void hi() {
+ __hi( __swiftObject );
+ }
+
+ public native void __finalize( long __swiftObject );
+
+ public void finalize() {
+ __finalize( __swiftObject );
+ }
+
+}
diff --git a/Android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..c7bd21d
--- /dev/null
+++ b/Android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Android/app/src/main/res/drawable/ic_arrow_back.xml b/Android/app/src/main/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..d426473
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Android/app/src/main/res/drawable/ic_bluetooth.xml b/Android/app/src/main/res/drawable/ic_bluetooth.xml
new file mode 100644
index 0000000..cb32298
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_bluetooth.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Android/app/src/main/res/drawable/ic_launcher_background.xml b/Android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..d5fccc5
--- /dev/null
+++ b/Android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Android/app/src/main/res/layout/list_cell.xml b/Android/app/src/main/res/layout/list_cell.xml
new file mode 100644
index 0000000..9c176dd
--- /dev/null
+++ b/Android/app/src/main/res/layout/list_cell.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/layout/list_header.xml b/Android/app/src/main/res/layout/list_header.xml
new file mode 100644
index 0000000..0877334
--- /dev/null
+++ b/Android/app/src/main/res/layout/list_header.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/layout/peripheral_item.xml b/Android/app/src/main/res/layout/peripheral_item.xml
new file mode 100644
index 0000000..dcf6f20
--- /dev/null
+++ b/Android/app/src/main/res/layout/peripheral_item.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/layout/peripheral_item_2.xml b/Android/app/src/main/res/layout/peripheral_item_2.xml
new file mode 100644
index 0000000..2cb01ae
--- /dev/null
+++ b/Android/app/src/main/res/layout/peripheral_item_2.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..93690f9
Binary files /dev/null and b/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100755
index 0000000..6e2ae02
Binary files /dev/null and b/Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..a039e07
Binary files /dev/null and b/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100755
index 0000000..e52115f
Binary files /dev/null and b/Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..15dea2a
Binary files /dev/null and b/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100755
index 0000000..a9a8610
Binary files /dev/null and b/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..2175529
Binary files /dev/null and b/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100755
index 0000000..e4fd6c1
Binary files /dev/null and b/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..bb63a5e
Binary files /dev/null and b/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100755
index 0000000..4a6e4a8
Binary files /dev/null and b/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/Android/app/src/main/res/values/colors.xml b/Android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/Android/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/Android/app/src/main/res/values/strings.xml b/Android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1c0f157
--- /dev/null
+++ b/Android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Bluetooth Explorer
+
diff --git a/Android/app/src/main/res/values/styles.xml b/Android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..c34b820
--- /dev/null
+++ b/Android/app/src/main/res/values/styles.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Android/app/src/main/swift/Package.resolved b/Android/app/src/main/swift/Package.resolved
new file mode 100644
index 0000000..4234c58
--- /dev/null
+++ b/Android/app/src/main/swift/Package.resolved
@@ -0,0 +1,97 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "Android",
+ "repositoryURL": "git@github.com:PureSwift/Android.git",
+ "state": {
+ "branch": "master",
+ "revision": "c5a6aea60296966fcf321c8b19184eb2d4ddd7b8",
+ "version": null
+ }
+ },
+ {
+ "package": "AndroidUIKit",
+ "repositoryURL": "git@github.com:PureSwift/AndroidUIKit.git",
+ "state": {
+ "branch": "master",
+ "revision": "d6e6ecfdd9988b0588109fb78767e39517db0573",
+ "version": null
+ }
+ },
+ {
+ "package": "Bluetooth",
+ "repositoryURL": "https://github.com/PureSwift/Bluetooth.git",
+ "state": {
+ "branch": null,
+ "revision": "8dc48df9cafe80cd97aadc5fd5dca0b18571d0f2",
+ "version": "2.5.2"
+ }
+ },
+ {
+ "package": "BluetoothLinux",
+ "repositoryURL": "https://github.com/PureSwift/BluetoothLinux.git",
+ "state": {
+ "branch": null,
+ "revision": "881c319763206f60e26bac11d21272e19c995f7e",
+ "version": "3.2.0"
+ }
+ },
+ {
+ "package": "CJavaVM",
+ "repositoryURL": "https://github.com/SwiftJava/CJavaVM.git",
+ "state": {
+ "branch": null,
+ "revision": "6240b2a563f02d98a162d4dce856b6c9a7196c90",
+ "version": "1.1.4"
+ }
+ },
+ {
+ "package": "GATT",
+ "repositoryURL": "git@github.com:PureSwift/GATT.git",
+ "state": {
+ "branch": "master",
+ "revision": "2841502875c91ba0fb3314951feed12054d70ac5",
+ "version": null
+ }
+ },
+ {
+ "package": "java_lang",
+ "repositoryURL": "https://github.com/SwiftJava/java_lang.git",
+ "state": {
+ "branch": null,
+ "revision": "3e3404bcdc29d6bc03ceed17d95e05f0254f7679",
+ "version": "2.3.2"
+ }
+ },
+ {
+ "package": "java_swift",
+ "repositoryURL": "https://github.com/SwiftJava/java_swift.git",
+ "state": {
+ "branch": null,
+ "revision": "f6745e8690d10ca01e983ceeb1339b0899fa8a99",
+ "version": "2.3.3"
+ }
+ },
+ {
+ "package": "java_util",
+ "repositoryURL": "https://github.com/SwiftJava/java_util.git",
+ "state": {
+ "branch": null,
+ "revision": "496b963217a7bf3b3c028db11ede5cfcbb3540e6",
+ "version": "2.3.2"
+ }
+ },
+ {
+ "package": "JNI",
+ "repositoryURL": "https://github.com/PureSwift/JNI.git",
+ "state": {
+ "branch": null,
+ "revision": "983ece85363e1c26ad5719404502d81b0b13f2d5",
+ "version": "1.0.3"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/Android/app/src/main/swift/Package.swift b/Android/app/src/main/swift/Package.swift
new file mode 100644
index 0000000..3060323
--- /dev/null
+++ b/Android/app/src/main/swift/Package.swift
@@ -0,0 +1,20 @@
+// swift-tools-version:4.0
+import PackageDescription
+
+let package = Package(
+ name: "BluetoothExplorer",
+ products: [
+ .library(name: "BluetoothExplorer", type: .dynamic, targets: ["BluetoothExplorerAndroid"]),
+ ],
+ dependencies: [
+ .package(url: "git@github.com:PureSwift/AndroidUIKit.git", .branch("master")),
+ .package(url: "git@github.com:PureSwift/GATT.git", .branch("master"))
+ ],
+ targets: [
+ .target(
+ name: "BluetoothExplorerAndroid",
+ dependencies: ["GATT", "AndroidUIKit"],
+ path: "Sources"
+ ),
+ ]
+)
diff --git a/BluetoothExplorer/Controller/Protocols/ActivityIndicatorViewController.swift b/Android/app/src/main/swift/Sources/ActivityIndicatorViewController.swift
old mode 100644
new mode 100755
similarity index 94%
rename from BluetoothExplorer/Controller/Protocols/ActivityIndicatorViewController.swift
rename to Android/app/src/main/swift/Sources/ActivityIndicatorViewController.swift
index c9d1ba3..0a40b42
--- a/BluetoothExplorer/Controller/Protocols/ActivityIndicatorViewController.swift
+++ b/Android/app/src/main/swift/Sources/ActivityIndicatorViewController.swift
@@ -7,7 +7,13 @@
//
import Foundation
+
+#if os(iOS)
import UIKit
+#elseif os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
protocol ActivityIndicatorViewController: class {
@@ -59,7 +65,7 @@ extension ActivityIndicatorViewController {
// show error
- print("⚠️ Error: \(error)")
+ log("⚠️ Error: \(error)")
if controller.view.window != nil {
diff --git a/Android/app/src/main/swift/Sources/AndroidCentral.swift b/Android/app/src/main/swift/Sources/AndroidCentral.swift
new file mode 100644
index 0000000..1442ea1
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/AndroidCentral.swift
@@ -0,0 +1,975 @@
+//
+// AndroidCentral.swift
+// Android
+//
+// Created by Marco Estrella on 7/24/18.
+//
+
+import Foundation
+import GATT
+import Bluetooth
+
+//#if os(android)
+
+import Android
+import java_swift
+import java_util
+
+public enum AndroidCentralError: Error {
+
+ /// Bluetooth is disabled.
+ case bluetoothDisabled
+
+ /// Binder IPC failure.
+ case binderFailure
+
+ /// Characteristic not found
+ case characteristicNotFound
+
+ /// Unexpected null value.
+ case nullValue(AnyKeyPath)
+}
+
+public final class AndroidCentral: CentralProtocol {
+
+ // MARK: - Properties
+
+ public var log: ((String) -> ())?
+
+ public let hostController: Android.Bluetooth.Adapter
+
+ public let context: Android.Content.Context
+
+ public let options: Options
+
+ internal private(set) var internalState = InternalState()
+
+ internal lazy var accessQueue: DispatchQueue = DispatchQueue(label: "\(type(of: self)) Access Queue")
+
+ // MARK: - Intialization
+
+ deinit {
+
+
+ }
+
+ public init(hostController: Android.Bluetooth.Adapter,
+ context: Android.Content.Context,
+ options: AndroidCentral.Options = Options()) {
+
+ self.hostController = hostController
+ self.context = context
+ self.options = options
+ }
+
+ // MARK: - Methods
+
+ public func scan(filterDuplicates: Bool = true,
+ shouldContinueScanning: () -> (Bool),
+ foundDevice: @escaping (ScanData) -> ()) throws {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ guard hostController.isEnabled()
+ else { throw AndroidCentralError.bluetoothDisabled }
+
+ guard let scanner = hostController.lowEnergyScanner
+ else { throw AndroidCentralError.nullValue(\Android.Bluetooth.Adapter.lowEnergyScanner) }
+
+ accessQueue.sync { [unowned self] in
+ self.internalState.scan.peripherals.removeAll()
+ self.internalState.scan.foundDevice = foundDevice
+ }
+
+ log?("Scanning...")
+
+ let scanCallback = ScanCallback()
+ scanCallback.central = self
+
+ scanner.startScan(callback: scanCallback)
+
+ // wait until finish scanning
+ while shouldContinueScanning() { sleep(1) }
+
+ scanner.stopScan(callback: scanCallback)
+ }
+
+ public func connect(to peripheral: Peripheral, timeout: TimeInterval = .gattDefaultTimeout) throws {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ guard hostController.isEnabled()
+ else { throw AndroidCentralError.bluetoothDisabled }
+
+ // store semaphore
+ let semaphore = Semaphore(timeout: timeout)
+ accessQueue.sync { [unowned self] in self.internalState.connect.semaphore = semaphore }
+ defer { accessQueue.sync { [unowned self] in self.internalState.connect.semaphore = nil } }
+
+ // attempt to connect (does not timeout)
+ try accessQueue.sync { [unowned self] in
+
+ guard let scanDevice = self.internalState.scan.peripherals[peripheral]
+ else { throw CentralError.unknownPeripheral }
+
+ let callback = GattCallback(central: self)
+
+ let gatt: AndroidBluetoothGatt
+
+ // call the correct method for connecting
+ if Android.OS.Build.Version.Sdk.sdkInt.rawValue <= Android.OS.Build.VersionCodes.lollipopMr1 {
+
+ gatt = scanDevice.scanResult.device.connectGatt(context: self.context,
+ autoConnect: false,
+ callback: callback)
+ } else {
+
+ gatt = scanDevice.scanResult.device.connectGatt(context: self.context,
+ autoConnect: false,
+ callback: callback,
+ transport: Android.Bluetooth.Device.Transport.le)
+ }
+
+ self.internalState.cache[peripheral] = Cache(gatt: gatt, callback: callback)
+ }
+
+ // throw async error
+ do { try semaphore.wait() }
+
+ catch CentralError.timeout {
+
+ // cancel connection if we timeout
+ accessQueue.sync { [unowned self] in
+
+ // Close, disconnect or cancel connection
+ self.internalState.cache[peripheral]?.gatt.disconnect()
+ self.internalState.cache[peripheral] = nil
+ }
+
+ throw CentralError.timeout
+ }
+
+ // negotiate MTU
+ let currentMTU = try self.maximumTransmissionUnit(for: peripheral)
+ if options.maximumTransmissionUnit != currentMTU {
+
+ log?("Current MTU is \(currentMTU), requesting \(options.maximumTransmissionUnit)")
+
+ try request(mtu: options.maximumTransmissionUnit, for: peripheral)
+ }
+ }
+
+ internal func request(mtu: ATTMaximumTransmissionUnit, for peripheral: Peripheral) throws {
+
+ try accessQueue.sync { [unowned self] in
+
+ guard let _ = self.internalState.scan.peripherals[peripheral]
+ else { throw CentralError.unknownPeripheral }
+
+ guard let cache = self.internalState.cache[peripheral]
+ else { throw CentralError.disconnected }
+
+ guard cache.gatt.requestMtu(mtu: Int(mtu.rawValue))
+ else { throw AndroidCentralError.binderFailure }
+ }
+
+ // dont wait
+ }
+
+ public func disconnect(peripheral: Peripheral) {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ accessQueue.sync { [unowned self] in
+ self.internalState.cache[peripheral]?.gatt.disconnect()
+ //self.internalState.cache[peripheral]?.gatt.close()
+ self.internalState.cache[peripheral] = nil
+ }
+ }
+
+ public func disconnectAll() {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ accessQueue.sync { [unowned self] in
+ self.internalState.cache.values.forEach {
+ $0.gatt.disconnect()
+ }
+ self.internalState.cache.removeAll()
+ }
+ }
+
+ public func discoverServices(_ services: [BluetoothUUID] = [],
+ for peripheral: Peripheral,
+ timeout: TimeInterval = .gattDefaultTimeout) throws -> [Service] {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ guard hostController.isEnabled()
+ else { throw AndroidCentralError.bluetoothDisabled }
+
+ // store semaphore
+ let semaphore = Semaphore(timeout: timeout)
+ accessQueue.sync { [unowned self] in self.internalState.discoverServices.semaphore = semaphore }
+ defer { accessQueue.sync { [unowned self] in self.internalState.discoverServices.semaphore = nil } }
+
+ try accessQueue.sync { [unowned self] in
+
+ guard self.internalState.scan.peripherals.keys.contains(peripheral)
+ else { throw CentralError.unknownPeripheral }
+
+ guard let cache = self.internalState.cache[peripheral]
+ else { throw CentralError.disconnected }
+
+ guard cache.gatt.discoverServices()
+ else { throw AndroidCentralError.binderFailure }
+ }
+
+ // throw async error
+ do { try semaphore.wait() }
+
+ // get values from internal state
+ return try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[peripheral]
+ else { throw CentralError.unknownPeripheral }
+
+ return cache.services.values.map { identifier, service in
+
+ let uuid = BluetoothUUID(android: service.getUuid())
+
+ let isPrimary = service.getType() == AndroidBluetoothGattService.ServiceType.primary
+
+ let service = Service(identifier: identifier,
+ uuid: uuid,
+ peripheral: peripheral,
+ isPrimary: isPrimary)
+
+ return service
+ }
+ }
+ }
+
+ public func discoverCharacteristics(_ characteristics: [BluetoothUUID] = [],
+ for service: Service,
+ timeout: TimeInterval = .gattDefaultTimeout) throws -> [Characteristic] {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ return try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[service.peripheral]
+ else { throw CentralError.disconnected }
+
+ guard let gattService = cache.services.values[service.identifier]
+ else { throw AndroidCentralError.binderFailure }
+
+ let gattCharacteristics = gattService.getCharacteristics()
+
+ internalState.cache[service.peripheral]?.update(gattCharacteristics, for: service)
+
+ return internalState.cache[service.peripheral]!.characteristics.values.map { (identifier, characteristic) in
+
+ let uuid = BluetoothUUID(android: characteristic.attribute.getUuid())
+
+ let properties = BitMaskOptionSet(rawValue: UInt8(characteristic.attribute.getProperties()))
+
+ let characteristic = Characteristic(identifier: identifier,
+ uuid: uuid,
+ peripheral: service.peripheral,
+ properties: properties)
+ return characteristic
+ }
+ }
+ }
+
+ public func readValue(for characteristic: Characteristic, timeout: TimeInterval = .gattDefaultTimeout) throws -> Data {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ guard hostController.isEnabled()
+ else { throw AndroidCentralError.bluetoothDisabled }
+
+ // store semaphore
+ let semaphore = Semaphore(timeout: timeout)
+ accessQueue.sync { [unowned self] in self.internalState.readCharacteristic.semaphore = semaphore }
+ defer { accessQueue.sync { [unowned self] in self.internalState.readCharacteristic.semaphore = nil } }
+
+ try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[characteristic.peripheral]
+ else { throw CentralError.disconnected }
+
+ guard let gattCharacteristic = cache.characteristics.values[characteristic.identifier]?.attribute
+ else { throw AndroidCentralError.characteristicNotFound }
+
+ guard cache.gatt.readCharacteristic(characteristic: gattCharacteristic)
+ else { throw AndroidCentralError.binderFailure }
+ }
+
+ // throw async error
+ do { try semaphore.wait() }
+
+ // get values from internal state
+ return try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[characteristic.peripheral]
+ else { throw CentralError.unknownPeripheral }
+
+ guard let readCharacteristic = cache.readCharacteristic
+ else { throw CentralError.invalidAttribute(characteristic.uuid) }
+
+ if let value = readCharacteristic.getValue() {
+
+ return Data(unsafeBitCast(value, to: [UInt8].self))
+ } else {
+
+ return Data()
+ }
+ }
+ }
+
+ public func writeValue(_ data: Data, for characteristic: Characteristic, withResponse: Bool = true, timeout: TimeInterval = .gattDefaultTimeout) throws {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ guard hostController.isEnabled()
+ else { throw AndroidCentralError.bluetoothDisabled }
+
+ // store semaphore
+ let semaphore = Semaphore(timeout: timeout)
+ accessQueue.sync { [unowned self] in self.internalState.writeCharacteristic.semaphore = semaphore }
+ defer { accessQueue.sync { [unowned self] in self.internalState.writeCharacteristic.semaphore = nil } }
+
+ try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[characteristic.peripheral]
+ else { throw CentralError.disconnected }
+
+ guard let gattCharacteristic = cache.characteristics.values[characteristic.identifier]?.attribute
+ else { throw AndroidCentralError.characteristicNotFound }
+
+ let dataArray = [UInt8](data)
+
+ let _ = gattCharacteristic.setValue(value: unsafeBitCast(dataArray, to: [Int8].self))
+
+ guard cache.gatt.writeCharacteristic(characteristic: gattCharacteristic)
+ else { throw AndroidCentralError.binderFailure }
+ }
+
+ // throw async error
+ do { try semaphore.wait() }
+
+ }
+
+ public func notify(_ notification: ((Data) -> ())?, for characteristic: Characteristic, timeout: TimeInterval = .gattDefaultTimeout) throws {
+ NSLog("\(type(of: self)) \(#function) started")
+
+ guard hostController.isEnabled()
+ else { throw AndroidCentralError.bluetoothDisabled }
+
+ let enable = notification != nil
+
+ // store semaphore
+ let semaphore = Semaphore(timeout: timeout)
+ accessQueue.sync { [unowned self] in self.internalState.notify.semaphore = semaphore }
+ defer { accessQueue.sync { [unowned self] in self.internalState.notify.semaphore = nil } }
+
+ try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[characteristic.peripheral]
+ else { throw CentralError.disconnected }
+
+ guard let gattCharacteristic = cache.characteristics.values[characteristic.identifier]?.attribute
+ else { throw AndroidCentralError.characteristicNotFound }
+
+ guard cache.gatt.setCharacteristicNotification(characteristic: gattCharacteristic, enable: enable) else {
+ throw AndroidCentralError.binderFailure
+ }
+
+ let uuid = java_util.UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
+
+ guard let descriptor = gattCharacteristic.getDescriptor(uuid: uuid!) else {
+ NSLog("AndroidCentral ERROR: descriptor doesnt exits")
+ throw AndroidCentralError.binderFailure
+ }
+
+ let valueEnableNotification : [Int8] = enable ? AndroidBluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : AndroidBluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
+
+ let wasLocallyStored = descriptor.setValue(valueEnableNotification)
+
+ guard cache.gatt.writeDescriptor(descriptor: descriptor) else {
+ throw AndroidCentralError.binderFailure
+ }
+
+ NSLog("\(type(of: self)) \(#function) \(enable ? "start": "stop") : true , locallyStored: \(wasLocallyStored)")
+ }
+
+ // throw async error
+ do { try semaphore.wait() }
+
+ try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[characteristic.peripheral]
+ else { throw CentralError.disconnected }
+
+ cache.update(identifier: characteristic.identifier, notification: notification)
+ }
+
+ NSLog("\(type(of: self)) \(#function) finished")
+ }
+
+ public func maximumTransmissionUnit(for peripheral: Peripheral) throws -> ATTMaximumTransmissionUnit {
+
+ guard hostController.isEnabled()
+ else { throw AndroidCentralError.bluetoothDisabled }
+
+ // access the cached value
+ return try accessQueue.sync { [unowned self] in
+
+ guard let cache = self.internalState.cache[peripheral]
+ else { throw CentralError.disconnected }
+
+ return cache.maximumTransmissionUnit
+ }
+ }
+
+ //MARK: Android
+
+ private class ScanCallback: Android.Bluetooth.LE.ScanCallback {
+
+ weak var central: AndroidCentral?
+
+ public required init(javaObject: jobject?) {
+ super.init(javaObject: javaObject)
+ }
+
+ convenience init() {
+
+ self.init(javaObject: nil)
+ bindNewJavaObject()
+ }
+
+ public override func onScanResult(callbackType: Android.Bluetooth.LE.ScanCallbackType,
+ result: Android.Bluetooth.LE.ScanResult) {
+
+ central?.log?("\(type(of: self)) \(#function) name: \(result.device.getName() ?? "") address: \(result.device.address)")
+
+ let peripheral = Peripheral(identifier: result.device.address)
+
+ let record = result.scanRecord
+
+ guard let advertisement = AdvertisementData(android: Data(record.bytes))
+ else { central?.log?("\(#function) Could not initialize advertisement data from \(record.bytes)"); return }
+
+ let isConnectable: Bool
+
+ if AndroidBuild.Version.Sdk.sdkInt.rawValue >= AndroidBuild.VersionCodes.O {
+
+ isConnectable = result.isConnectable
+
+ } else {
+
+ isConnectable = true // FIXME: ??
+ }
+
+ let scanData = ScanData(peripheral: peripheral,
+ date: Date(),
+ rssi: Double(result.rssi),
+ advertisementData: advertisement,
+ isConnectable: isConnectable)
+
+ central?.accessQueue.async { [weak self] in
+
+ guard let central = self?.central
+ else { return }
+ central.internalState.scan.foundDevice?(scanData)
+ central.internalState.scan.peripherals[peripheral] = InternalState.Scan.Device(scanData: scanData,
+ scanResult: result)
+ }
+ }
+
+ public override func onBatchScanResults(results: [Android.Bluetooth.LE.ScanResult]) {
+
+ central?.log?("\(type(of: self)): \(#function)")
+ }
+
+ public override func onScanFailed(error: AndroidBluetoothLowEnergyScanCallback.Error) {
+
+ central?.log?("\(type(of: self)): \(#function)")
+ }
+ }
+
+ public class GattCallback: Android.Bluetooth.GattCallback {
+
+ private weak var central: AndroidCentral?
+
+ convenience init(central: AndroidCentral) {
+ self.init(javaObject: nil)
+ bindNewJavaObject()
+
+ self.central = central
+ }
+
+ public required init(javaObject: jobject?) {
+ super.init(javaObject: javaObject)
+ }
+
+ public override func onConnectionStateChange(gatt: Android.Bluetooth.Gatt,
+ status: AndroidBluetoothGatt.Status,
+ newState: AndroidBluetoothDevice.State) {
+
+ central?.log?("\(type(of: self)): \(#function)")
+
+ central?.log?("Status: \(status) - newState = \(newState)")
+
+ central?.accessQueue.async { [weak self] in
+
+ guard let central = self?.central
+ else { return }
+
+ switch (status, newState) {
+
+ case (.success, .connected):
+
+ central.log?("GATT Connected")
+
+ // if we are expecting a new connection
+ if central.internalState.connect.semaphore != nil {
+
+ central.internalState.connect.semaphore?.stopWaiting()
+ central.internalState.connect.semaphore = nil
+ }
+
+ case (.success, .disconnected):
+
+ central.log?("GATT Disconnected")
+
+ break // nothing for now
+
+ default:
+
+ central.log?("GATT Status Error")
+
+ central.internalState.connect.semaphore?.stopWaiting(status) // throw `status` error
+ }
+ }
+ }
+
+ public override func onServicesDiscovered(gatt: Android.Bluetooth.Gatt,
+ status: AndroidBluetoothGatt.Status) {
+
+ let peripheral = Peripheral(gatt)
+
+ central?.log?("\(type(of: self)): \(#function)")
+
+ central?.log?("\(peripheral) Status: \(status)")
+
+ central?.accessQueue.async { [weak self] in
+
+ guard let central = self?.central
+ else { return }
+
+ guard status == .success
+ else { central.internalState.discoverServices.semaphore?.stopWaiting(status); return }
+
+ central.internalState.cache[peripheral]?.update(gatt.services)
+
+ // success
+ central.internalState.discoverServices.semaphore?.stopWaiting()
+ central.internalState.discoverServices.semaphore = nil
+ }
+ }
+
+ public override func onCharacteristicChanged(gatt: Android.Bluetooth.Gatt, characteristic: Android.Bluetooth.GattCharacteristic) {
+
+ central?.log?("\(type(of: self)): \(#function)")
+
+ let peripheral = Peripheral(gatt)
+
+ guard let cache = central?.internalState.cache[peripheral] else {
+ assertionFailure("Invalid cache for \(characteristic.getUuid().toString())"); return
+ }
+
+ central?.log?("characteristics count: \(cache.characteristics.values.count)")
+
+ let identifier = UInt(bitPattern: characteristic.getUuid().toString().hashValue ^ characteristic.getInstanceId())
+
+ guard let characteristicCache = cache.characteristics.values[identifier] else {
+ assertionFailure("Invalid identifier for \(characteristic.getUuid().toString())"); return
+ }
+
+ guard let notification = characteristicCache.notification else {
+ assertionFailure("Unexpected notification for \(characteristic.getUuid().toString())"); return
+ }
+
+ if let value = characteristic.getValue() {
+
+ notification(Data(unsafeBitCast(value, to: [UInt8].self)))
+ } else {
+
+ notification(Data())
+ }
+ }
+
+ public override func onCharacteristicRead(gatt: Android.Bluetooth.Gatt, characteristic: Android.Bluetooth.GattCharacteristic, status: AndroidBluetoothGatt.Status) {
+
+ central?.log?("\(type(of: self)): \(#function)")
+
+ let peripheral = Peripheral(gatt)
+
+ central?.log?("\(type(of: self)): \(#function) got peripheral")
+
+ central?.log?("\(peripheral) Status: \(status)")
+
+ central?.accessQueue.async { [weak self] in
+
+ guard let central = self?.central
+ else { return }
+
+ central.log?("\(type(of: self)): \(#function) got centrar again")
+
+ guard status == .success
+ else { central.internalState.readCharacteristic.semaphore?.stopWaiting(status); return }
+
+ central.log?("\(type(of: self)): \(#function) status: \(status)")
+
+ central.internalState.cache[peripheral]?.update(characteristic)
+
+ central.log?("\(type(of: self)): \(#function) characteristic was updated on cache")
+
+ // success
+ central.internalState.readCharacteristic.semaphore?.stopWaiting()
+ }
+ }
+
+ public override func onCharacteristicWrite(gatt: Android.Bluetooth.Gatt, characteristic: Android.Bluetooth.GattCharacteristic, status: AndroidBluetoothGatt.Status) {
+
+ central?.log?("\(type(of: self)): \(#function)")
+
+ let peripheral = Peripheral(gatt)
+
+ central?.log?("\(peripheral) Status: \(status)")
+
+ central?.accessQueue.async { [weak self] in
+
+ guard let central = self?.central
+ else { return }
+
+ guard status == .success
+ else { central.internalState.writeCharacteristic.semaphore?.stopWaiting(status); return }
+
+ // success
+ central.internalState.writeCharacteristic.semaphore?.stopWaiting()
+ central.internalState.writeCharacteristic.semaphore = nil
+ }
+ }
+
+ public override func onDescriptorRead(gatt: Android.Bluetooth.Gatt, descriptor: Android.Bluetooth.GattDescriptor, status: AndroidBluetoothGatt.Status) {
+
+ central?.log?("\(type(of: self)): \(#function)")
+
+ }
+
+ public override func onDescriptorWrite(gatt: Android.Bluetooth.Gatt, descriptor: Android.Bluetooth.GattDescriptor, status: AndroidBluetoothGatt.Status) {
+
+ central?.log?(" \(type(of: self)): \(#function) started")
+
+ let peripheral = Peripheral(gatt)
+
+ central?.log?("\(type(of: self)): \(#function) - \(peripheral) Status: \(status)")
+
+ central?.accessQueue.async { [weak self] in
+
+ guard let central = self?.central
+ else { return }
+
+ guard status == .success else {
+ central.log?(" \(type(of: self)): \(#function) operation failed")
+ central.internalState.notify.semaphore?.stopWaiting(status);
+ return
+ }
+
+ // success
+ central.log?(" \(type(of: self)): \(#function) finish")
+ central.internalState.notify.semaphore?.stopWaiting()
+ }
+ }
+
+ public override func onMtuChanged(gatt: Android.Bluetooth.Gatt,
+ mtu: Int,
+ status: Android.Bluetooth.Gatt.Status) {
+
+ central?.log?("\(type(of: self)): \(#function) Peripheral \(Peripheral(gatt)) MTU \(mtu) Status \(status)")
+
+ let peripheral = Peripheral(gatt)
+
+ central?.accessQueue.async { [weak self] in
+
+ guard let central = self?.central
+ else { return }
+
+ // get new MTU value
+ guard let newMTU = ATTMaximumTransmissionUnit(rawValue: UInt16(mtu))
+ else { fatalError("Invalid MTU \(mtu)") }
+
+ // cache new MTU value
+ central.internalState.cache[peripheral]?.maximumTransmissionUnit = newMTU
+ }
+ }
+
+ public override func onPhyRead(gatt: Android.Bluetooth.Gatt, txPhy: AndroidBluetoothGatt.TxPhy, rxPhy: AndroidBluetoothGatt.RxPhy, status: AndroidBluetoothGatt.Status) {
+
+ NSLog("\(type(of: self)): \(#function)")
+
+ }
+
+ public override func onPhyUpdate(gatt: Android.Bluetooth.Gatt, txPhy: AndroidBluetoothGatt.TxPhy, rxPhy: AndroidBluetoothGatt.RxPhy, status: AndroidBluetoothGatt.Status) {
+
+ NSLog("\(type(of: self)): \(#function)")
+
+ }
+
+ public override func onReadRemoteRssi(gatt: Android.Bluetooth.Gatt, rssi: Int, status: AndroidBluetoothGatt.Status) {
+ NSLog("\(type(of: self)): \(#function)")
+
+ }
+
+ public override func onReliableWriteCompleted(gatt: Android.Bluetooth.Gatt, status: AndroidBluetoothGatt.Status) {
+ NSLog("\(type(of: self)): \(#function)")
+
+ }
+ }
+
+}
+
+// MARK: - Supporting Types
+
+public extension AndroidCentral {
+
+ /// Android GATT Central options
+ public struct Options {
+
+ public let maximumTransmissionUnit: ATTMaximumTransmissionUnit
+
+ public init(maximumTransmissionUnit: ATTMaximumTransmissionUnit = .default) {
+
+ self.maximumTransmissionUnit = maximumTransmissionUnit
+ }
+ }
+}
+
+// MARK: - Private Supporting Types
+
+internal extension AndroidCentral {
+
+ struct InternalState {
+
+ fileprivate init() { }
+
+ var cache = [Peripheral: Cache]()
+
+ var scan = Scan()
+
+ struct Scan {
+
+ var peripherals = [Peripheral: Device]()
+
+ var foundDevice: ((ScanData) -> ())?
+
+ struct Device {
+
+ let scanData: ScanData
+
+ let scanResult: Android.Bluetooth.LE.ScanResult
+ }
+ }
+
+ var connect = Connect()
+
+ struct Connect {
+
+ var semaphore: Semaphore?
+ }
+
+ var discoverServices = DiscoverServices()
+
+ struct DiscoverServices {
+
+ var semaphore: Semaphore?
+ }
+
+ var discoverCharacteristics = DiscoverCharacteristics()
+
+ struct DiscoverCharacteristics {
+
+ var semaphore: Semaphore?
+ }
+
+ var readCharacteristic = ReadCharacteristic()
+
+ struct ReadCharacteristic {
+
+ var semaphore: Semaphore?
+ }
+
+ var writeCharacteristic = WriteCharacteristic()
+
+ struct WriteCharacteristic {
+
+ var semaphore: Semaphore?
+ }
+
+ var notify = Notify()
+
+ struct Notify {
+
+ var semaphore: Semaphore?
+ }
+ }
+}
+
+internal extension AndroidCentral {
+
+ /// GATT cache for a connection or peripheral.
+ final class Cache {
+
+ fileprivate init(gatt: Android.Bluetooth.Gatt,
+ callback: GattCallback) {
+
+ self.gatt = gatt
+ self.gattCallback = callback
+ }
+
+ let gattCallback: GattCallback
+
+ let gatt: Android.Bluetooth.Gatt
+
+ fileprivate(set) var maximumTransmissionUnit: ATTMaximumTransmissionUnit = .default
+
+ var services = Services()
+
+ var characteristics = Characteristics()
+
+ var readCharacteristic: Android.Bluetooth.GattCharacteristic?
+
+ struct Characteristics {
+
+ fileprivate(set) var values: [UInt: (attribute: Android.Bluetooth.GattCharacteristic, notification: ((Data) -> ())?)] = [:]
+ }
+
+ struct Services {
+
+ fileprivate(set) var values: [UInt: Android.Bluetooth.GattService] = [:]
+ }
+
+ fileprivate func update(_ newValues: [Android.Bluetooth.GattService]) {
+
+ services.values.removeAll()
+
+ newValues.forEach {
+
+ let identifier = UInt(bitPattern: $0.getUuid().toString().hashValue ^ $0.getInstanceId())
+ services.values[identifier] = $0
+ }
+ }
+
+ fileprivate func update(_ newValues: [Android.Bluetooth.GattCharacteristic], for service: Service) {
+
+ newValues.forEach {
+
+ let identifier = UInt(bitPattern: $0.getUuid().toString().hashValue ^ $0.getInstanceId())
+ characteristics.values[identifier] = ($0, nil)
+ }
+ }
+
+ fileprivate func update(identifier: UInt, notification: ((Data) -> ())?){
+
+ let value = characteristics.values[identifier]
+
+ guard let characteristic = value
+ else { return }
+
+ characteristics.values[identifier] = (characteristic.attribute, notification )
+ }
+
+ fileprivate func update(_ newValue: Android.Bluetooth.GattCharacteristic) {
+
+ readCharacteristic = newValue
+ }
+ }
+}
+
+internal extension AndroidCentral {
+
+ final class Semaphore {
+
+ let semaphore: DispatchSemaphore
+ let timeout: TimeInterval
+ var error: Swift.Error?
+
+ init(timeout: TimeInterval) {
+
+ self.timeout = timeout
+ self.semaphore = DispatchSemaphore(value: 0)
+ self.error = nil
+ }
+
+ func wait() throws {
+
+ let dispatchTime: DispatchTime = .now() + timeout
+
+ let success = semaphore.wait(timeout: dispatchTime) == .success
+
+ if let error = self.error {
+
+ throw error
+ }
+
+ guard success else { throw CentralError.timeout }
+ }
+
+ func stopWaiting(_ error: Swift.Error? = nil) {
+
+ // store signal
+ self.error = error
+
+ // stop blocking
+ semaphore.signal()
+ }
+ }
+}
+
+// MARK: - Extentions
+
+fileprivate extension Peripheral {
+
+ init(_ device: AndroidBluetoothDevice) {
+
+ self.init(identifier: device.address)
+ }
+
+ init(_ gatt: AndroidBluetoothGatt) {
+
+ self.init(gatt.getDevice())
+ }
+}
+
+internal extension BluetoothUUID {
+
+ init(android javaUUID: java_util.UUID) {
+
+ let uuid = UUID(uuidString: javaUUID.toString())!
+
+ if let value = UInt16(bluetooth: uuid) {
+
+ self = .bit16(value)
+
+ } else {
+
+ self = .bit128(UInt128(uuid: uuid))
+ }
+ }
+}
+
+//#endif
diff --git a/Android/app/src/main/swift/Sources/AppDelegate.swift b/Android/app/src/main/swift/Sources/AppDelegate.swift
new file mode 100644
index 0000000..b3a02cb
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/AppDelegate.swift
@@ -0,0 +1,209 @@
+//
+// AppDelegate.swift
+// BluetoothExplorer
+//
+// Created by Alsey Coleman Miller on 9/7/18.
+// Copyright © 2018 PureSwift. All rights reserved.
+//
+
+import Foundation
+
+#if os(iOS)
+import UIKit
+#elseif os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
+
+import Bluetooth
+import GATT
+
+final class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate }
+
+ var window: UIWindow?
+
+ #if os(Android) || os(macOS)
+ var bluetoothEnabled: (() -> ())?
+ #endif
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+
+ #if os(iOS)
+ log("Launching Bluetooth Explorer v\(AppVersion) Build \(AppBuild)")
+ #elseif os(Android)
+ log("Launching Bluetooth Explorer")
+ #endif
+
+ #if os(Android) || os(macOS)
+ NSLog("UIScreen scale: \(UIScreen.main.scale)")
+ NSLog("UIScreen native scale: \(UIScreen.main.nativeScale)")
+ NSLog("UIScreen size: \(UIScreen.main.bounds.size)")
+ NSLog("UIScreen native size: \(UIScreen.main.nativeBounds.size)")
+ #endif
+
+ // initalize BLE
+ NativeCentral.shared.log = { log("Central: \($0)") }
+
+ // load window and view controller
+ let viewController = CentralViewController()
+
+ // setup UI theme
+ #if os(iOS)
+ configureAppearance()
+ #endif
+
+ let navigationController = UINavigationController(rootViewController: viewController)
+
+ self.window = UIWindow(frame: UIScreen.main.bounds)
+ self.window?.rootViewController = navigationController
+ self.window?.makeKeyAndVisible()
+
+ // request Bluetooth permissions on Android
+ #if os(Android) || os(macOS)
+ self.enableBluetooth()
+ #endif
+
+ return true
+ }
+
+ func applicationWillResignActive(_ application: UIApplication) {
+
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
+
+ NSLog("\(#function)")
+ }
+
+ func applicationDidEnterBackground(_ application: UIApplication) {
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+
+ NSLog("\(#function)")
+ }
+
+ func applicationWillEnterForeground(_ application: UIApplication) {
+ // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
+
+ NSLog("\(#function)")
+ }
+
+ func applicationDidBecomeActive(_ application: UIApplication) {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+
+ NSLog("\(#function)")
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+
+ NSLog("\(#function)")
+ }
+}
+
+// MARK: - iOS Info Plist
+
+#if os(iOS)
+/// Version of the app.
+public let AppVersion = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
+
+/// Build of the app.
+public let AppBuild = Int(Bundle.main.infoDictionary!["CFBundleVersion"] as! String)!
+#endif
+
+// MARK: - Android Permissions
+
+#if os(Android) || os(macOS)
+
+extension AppDelegate {
+
+ internal struct AndroidPermissionRequest {
+
+ static let enableBluetooth = 1000
+ static let gps = 2000
+ }
+}
+
+extension AppDelegate {
+
+ func application(_ application: UIApplication, activityResult requestCode: Int, resultCode: Int, data: Android.Content.Intent?) {
+
+ NSLog("\(type(of: self)) \(#function) - requestCode = \(requestCode) - resultCode = \(resultCode)")
+
+ if resultCode == AndroidPermissionRequest.enableBluetooth,
+ resultCode == SwiftSupportAppCompatActivity.RESULT_OK {
+
+ // no need to request permissions
+ if requestLocationPermissions() {
+
+ //
+ bluetoothEnabled?()
+ }
+ }
+ }
+
+ func application(_ application: UIApplication, requestPermissionsResult requestCode: Int, permissions: [String], grantResults: [Int]) {
+
+ NSLog("\(type(of: self)) \(#function)")
+
+ if requestCode == AndroidPermissionRequest.gps {
+
+ if grantResults[0] == Android.Content.PM.PackageManager.Permission.granted.rawValue {
+
+ // permission granted, now we can scan
+ bluetoothEnabled?()
+
+ } else {
+
+ NSLog(" \(type(of: self)) \(#function) GPS Permission is required")
+ }
+ }
+ }
+}
+
+extension AppDelegate {
+
+ /// Checks if permissions are needed.
+ @discardableResult
+ func enableBluetooth(hostController: Android.Bluetooth.Adapter = Android.Bluetooth.Adapter.default!) -> Bool {
+
+ guard hostController.isEnabled() == false
+ else { return requestLocationPermissions() }
+
+ let enableBluetoothIntent = Android.Content.Intent(action: Android.Bluetooth.Adapter.Action.requestEnable.rawValue)
+
+ UIApplication.shared.androidActivity.startActivityForResult(intent: enableBluetoothIntent,
+ requestCode: AndroidPermissionRequest.enableBluetooth)
+
+ log("\(type(of: self)) \(#function) enable Bluetooth")
+
+ return false
+ }
+
+ @discardableResult
+ func requestLocationPermissions() -> Bool {
+
+ let activity = UIApplication.shared.androidActivity
+
+ if Android.OS.Build.Version.Sdk.sdkInt.rawValue >= Android.OS.Build.VersionCodes.M,
+ activity.checkSelfPermission(permission: Android.ManifestPermission.accessCoarseLocation.rawValue) != Android.Content.PM.PackageManager.Permission.granted.rawValue {
+
+ log("\(type(of: self)) \(#function) request permission")
+
+ let permissions = [Android.ManifestPermission.accessCoarseLocation.rawValue]
+
+ activity.requestPermissions(permissions: permissions, requestCode: AndroidPermissionRequest.gps)
+
+ return false
+
+ } else {
+
+ log("\(type(of: self)) \(#function) dont request permission")
+
+ return true
+ }
+ }
+}
+
+#endif
diff --git a/Android/app/src/main/swift/Sources/Appearance.swift b/Android/app/src/main/swift/Sources/Appearance.swift
new file mode 100644
index 0000000..3692129
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/Appearance.swift
@@ -0,0 +1,31 @@
+//
+// Appearance.swift
+// BluetoothExplorer
+//
+// Created by Alsey Coleman Miller on 9/23/18.
+// Copyright © 2018 PureSwift. All rights reserved.
+//
+
+#if os(iOS)
+import UIKit
+#elseif os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
+
+/// Configure the application's UI appearance
+func configureAppearance() {
+
+ #if os(iOS)
+
+ if #available(iOS 11.0, *) {
+ UINavigationBar.appearance().prefersLargeTitles = true
+ UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.white]
+ }
+
+ UINavigationBar.appearance().tintColor = .white
+ UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white]
+ UINavigationBar.appearance().barTintColor = UIColor(red: 0.386, green: 0.707, blue: 1.0, alpha: 1.0)
+
+ #endif
+}
diff --git a/Android/app/src/main/swift/Sources/Async.swift b/Android/app/src/main/swift/Sources/Async.swift
new file mode 100755
index 0000000..dd9d8d7
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/Async.swift
@@ -0,0 +1,32 @@
+//
+// Async.swift
+// BluetoothExplorer
+//
+// Created by Alsey Coleman Miller on 6/19/18.
+// Copyright © 2018 PureSwift. All rights reserved.
+//
+
+import Foundation
+import Dispatch
+
+#if os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
+
+func mainQueue(_ block: @escaping () -> ()) {
+
+ #if os(iOS)
+ DispatchQueue.main.async(execute: block)
+ #elseif os(Android) || os(macOS)
+ UIApplication.shared.androidActivity.runOnMainThread(block)
+ #endif
+}
+
+/// Perform a task on the internal queue.
+func async(_ block: @escaping () -> ()) {
+
+ appQueue.async(execute: block)
+}
+
+let appQueue = DispatchQueue(label: "App Queue")
diff --git a/Android/app/src/main/swift/Sources/Central.swift b/Android/app/src/main/swift/Sources/Central.swift
new file mode 100644
index 0000000..eb5c1a5
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/Central.swift
@@ -0,0 +1,51 @@
+//
+// Central.swift
+// BluetoothExplorer
+//
+// Created by Alsey Coleman Miller on 9/7/18.
+// Copyright © 2018 PureSwift. All rights reserved.
+//
+
+import Foundation
+import Bluetooth
+import GATT
+
+#if os(macOS) || os(iOS)
+import DarwinGATT
+
+typealias NativeCentral = DarwinCentral
+
+private struct CentralCache {
+
+ static let options = DarwinCentral.Options(showPowerAlert: false, restoreIdentifier: nil)
+
+ static let central = DarwinCentral(options: options)
+}
+
+#elseif os(Android)
+
+import Android
+import AndroidUIKit
+
+typealias NativeCentral = AndroidCentral
+
+private struct CentralCache {
+
+ static let hostController = Android.Bluetooth.Adapter.default!
+
+ static let context = UIApplication.shared.androidActivity
+
+ static let options = AndroidCentral.Options()
+
+ static let central = AndroidCentral(hostController: hostController, context: context, options: options)
+}
+
+#endif
+
+internal extension NativeCentral {
+
+ static var shared: NativeCentral {
+
+ return CentralCache.central
+ }
+}
diff --git a/Android/app/src/main/swift/Sources/CentralViewController.swift b/Android/app/src/main/swift/Sources/CentralViewController.swift
new file mode 100644
index 0000000..e8026f6
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/CentralViewController.swift
@@ -0,0 +1,261 @@
+//
+// CentralViewController.swift
+// BluetoothExplorer
+//
+// Created by Alsey Coleman Miller on 9/7/18.
+// Copyright © 2018 PureSwift. All rights reserved.
+//
+
+import Foundation
+import Bluetooth
+import GATT
+
+#if os(iOS)
+import UIKit
+#elseif os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
+
+/// Scans for nearby BLE devices.
+final class CentralViewController: UITableViewController {
+
+ typealias NativeScanData = ScanData
+
+ // MARK: - Properties
+
+ private(set) var items = [NativeScanData]()
+
+ let scanDuration: TimeInterval = 10.0
+
+ let filterDuplicates: Bool = false
+
+ private let cellReuseIdentifier = "Cell"
+
+ // MARK: - Loading
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // set title
+ self.title = "Central"
+
+ // setup table view
+ self.tableView.estimatedRowHeight = 44
+ self.tableView.rowHeight = UITableViewAutomaticDimension
+ #if os(iOS)
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: self.cellReuseIdentifier)
+ #else
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: self.cellReuseIdentifier)
+ #endif
+
+ let refreshControl = UIRefreshControl(frame: .zero)
+
+ #if os(Android) || os(macOS)
+ refreshControl.addTarget(action: { [unowned self] in self.reloadData() }, for: .valueChanged)
+ #else
+ refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
+ #endif
+
+ self.refreshControl = refreshControl
+
+ #if os(Android) || os(macOS)
+ AppDelegate.shared.bluetoothEnabled = { [weak self] in self?.reloadData() }
+ reloadData()
+ #endif
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ reloadData()
+ }
+
+ // MARK: - Actions
+
+ #if os(iOS)
+ @objc func pullToRefresh(_ sender: UIRefreshControl) {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { [weak self] in
+
+ self?.reloadData()
+ })
+ }
+ #endif
+
+
+ // MARK: - Methods
+
+ private subscript (indexPath: IndexPath) -> NativeScanData {
+
+ @inline(__always)
+ get { return self.items[indexPath.row] }
+ }
+
+ private final func endRefreshing() {
+
+ if let refreshControl = self.refreshControl,
+ refreshControl.isRefreshing{
+
+ refreshControl.endRefreshing()
+ }
+ }
+
+ private func reloadData() {
+
+ log("\(type(of: self)) \(#function)")
+
+ // clear table data
+ self.items.removeAll()
+ tableView.reloadData()
+
+ // make sure its enabled
+ #if os(Android)
+ guard AndroidCentral.shared.hostController.isEnabled()
+ else { return } // wait until enabled
+ #endif
+
+ // scan
+ let scanDuration = self.scanDuration
+ let filterDuplicates = self.filterDuplicates
+
+ let start = Date()
+ let end = start + scanDuration
+
+ performActivity({
+ try NativeCentral.shared.scan(filterDuplicates: filterDuplicates,
+ shouldContinueScanning: { Date() < end },
+ foundDevice: { [weak self] (device) in mainQueue { self?.foundDevice(device) } })
+ })
+ }
+
+ private func foundDevice(_ scanData: NativeScanData) {
+
+ // remove old value
+ if let index = self.items.index(where: { $0.peripheral == scanData.peripheral }) {
+
+ self.items.remove(at: index)
+ }
+
+ // add item
+ self.items.append(scanData)
+
+ // sort
+ self.items.sort(by: { $0.peripheral.description < $1.peripheral.description })
+
+ // update table view
+ self.tableView.reloadData()
+ }
+
+ private func configure(cell: UITableViewCell, at indexPath: IndexPath) {
+
+ let item = self[indexPath]
+
+ #if os(iOS)
+ cell.textLabel?.text = item.advertisementData.localName ?? item.peripheral.identifier.description
+ #else
+
+ let cellTypeIndex = indexPath.row % 2 == 0 ? 0 : 1
+
+ if(cellTypeIndex == 0){
+
+ let layoutName = "peripheral_item"
+
+ if cell.layoutName != layoutName {
+ cell.inflateAndroidLayout(layoutName: layoutName)
+ }
+
+ let itemView = cell.getItemView()
+
+ let tvNameId = UIApplication.shared.androidActivity.getIdentifier(name: "tvName", type: "id")
+ let tvAddressId = UIApplication.shared.androidActivity.getIdentifier(name: "tvAddress", type: "id")
+
+ guard let tvNameObject = itemView.findViewById(tvNameId)
+ else { fatalError("No view for \(tvNameId)") }
+
+ guard let tvAddressObject = itemView.findViewById(tvAddressId)
+ else { fatalError("No view for \(tvAddressId)") }
+
+ let tvName = Android.Widget.TextView(casting: tvNameObject)
+ let tvAddress = Android.Widget.TextView(casting: tvAddressObject)
+
+ tvName?.text = item.advertisementData.localName ?? "NO NAME"
+ tvAddress?.text = item.peripheral.description
+ } else {
+
+ let layoutName = "peripheral_item_2"
+
+ if cell.layoutName != layoutName {
+ cell.inflateAndroidLayout(layoutName: layoutName)
+ }
+
+ let itemView = cell.getItemView()
+
+ let tvAddressId = UIApplication.shared.androidActivity.getIdentifier(name: "tvAddress", type: "id")
+
+ guard let tvAddressObject = itemView.findViewById(tvAddressId)
+ else { fatalError("No view for \(tvAddressId)") }
+
+ let tvAddress = Android.Widget.TextView(casting: tvAddressObject)
+
+ tvAddress?.text = item.peripheral.description
+ }
+
+ #endif
+ }
+
+ // MARK: - UITableViewDataSource
+
+ override func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return items.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
+ configure(cell: cell, at: indexPath)
+
+ return cell
+ }
+
+ // MARK: - UITableViewDelegate
+
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ #if os(iOS)
+ defer { tableView.deselectRow(at: indexPath, animated: true) }
+ #endif
+
+ let item = self[indexPath]
+
+ self.endRefreshing()
+
+ log("Selected \(item.peripheral) \(item.advertisementData.localName ?? "")")
+
+ let viewController = ServicesViewController(scanData: item)
+
+ self.show(viewController, sender: self)
+ }
+}
+
+// MARK: - ActivityIndicatorViewController
+
+extension CentralViewController: ActivityIndicatorViewController {
+
+ func showActivity() {
+
+ self.view.isUserInteractionEnabled = false
+ }
+
+ func hideActivity(animated: Bool = true) {
+
+ self.view.isUserInteractionEnabled = true
+ self.endRefreshing()
+ }
+}
diff --git a/Android/app/src/main/swift/Sources/CharacteristicViewController.swift b/Android/app/src/main/swift/Sources/CharacteristicViewController.swift
new file mode 100644
index 0000000..cd30e58
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/CharacteristicViewController.swift
@@ -0,0 +1,461 @@
+//
+// CharacteristicViewController.swift
+// BluetoothExplorerAndroid
+//
+// Created by Marco Estrella on 9/21/18.
+//
+
+import Foundation
+import Bluetooth
+import GATT
+
+#if os(iOS)
+import UIKit
+#elseif os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
+
+/// Characteristic
+final class CharacteristicViewController: UITableViewController {
+
+ typealias NativeService = Service
+ typealias NativeCharacteristic = Characteristic
+
+ // MARK: - Properties
+
+ let service: NativeService
+ let characteristic: NativeCharacteristic
+
+ private var sections = [Section]()
+
+ private(set) var characteristicValue = [Data]() {
+ didSet { configureView() }
+ }
+
+ private var isNotifying = false {
+ didSet { configureView() }
+ }
+
+ private let timeout: TimeInterval = .gattDefaultTimeout
+
+ // MARK: - Initialization
+
+ init(service: NativeService, characteristic: NativeCharacteristic) {
+
+ self.characteristic = characteristic
+ self.service = service
+
+ super.init(style: .grouped)
+ }
+
+ deinit {
+
+ // just in case we didnt stop notifications
+ NativeCentral.shared.disconnect(peripheral: characteristic.peripheral)
+ }
+
+ #if os(iOS)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+ #endif
+
+ private let cellReuseIdentifier = "Cell"
+
+ // MARK: - Loading
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ NSLog("\(type(of: self)) \(#function)")
+
+
+ // setup table view
+ self.tableView.estimatedRowHeight = 44
+ self.tableView.rowHeight = UITableViewAutomaticDimension
+ #if os(iOS)
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: CellIdentifier.uuid.rawValue)
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: CellIdentifier.name.rawValue)
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: CellIdentifier.value.rawValue)
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: CellIdentifier.property.rawValue)
+ #else
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: self.cellReuseIdentifier)
+ #endif
+
+ // update UI
+ self.configureView()
+
+ // attempt to read
+ self.readValue()
+ }
+
+ // MARK: - Methods
+
+ private subscript (indexPath: IndexPath) -> Item {
+
+ @inline(__always)
+ get { return self.sections[indexPath.section].items[indexPath.row] }
+ }
+
+ private func configureView() {
+
+ title = self.characteristic.uuid.description
+
+ // configure table view
+
+ sections = []
+
+ do {
+
+ var items = [Item]()
+
+ if let name = characteristic.uuid.name {
+
+ items.append(.name(name))
+ }
+
+ items.append(.uuid(characteristic.uuid))
+
+ sections.append(Section(title: "Information", items: items))
+ }
+
+ if characteristic.properties.isEmpty == false {
+
+ sections.append(Section(title: "Properties", items: characteristic.properties.map { Item.property($0) }))
+ }
+
+ if characteristicValue.isEmpty == false {
+
+ sections.append(Section(title: "Value", items: characteristicValue.reversed().map { Item.value($0) }))
+ }
+
+ // update UI
+ tableView.reloadData()
+ }
+
+ private func readValue() {
+ NSLog("\(type(of: self)) \(#function)")
+ let timeout = self.timeout
+ let service = self.service
+ let characteristic = self.characteristic
+ let peripheral = self.service.peripheral
+
+ guard characteristic.properties.contains(.read)
+ else { return }
+
+ performActivity({
+ try NativeCentral.shared.connect(to: peripheral, timeout: timeout)
+ defer { NativeCentral.shared.disconnect(peripheral: peripheral) }
+ let _ = try NativeCentral.shared.discoverServices(for: peripheral, timeout: timeout)
+ let _ = try NativeCentral.shared.discoverCharacteristics(for: service, timeout: timeout)
+ return try NativeCentral.shared.readValue(for: characteristic, timeout: timeout)
+ }, completion: {
+ $0.characteristicValue.append($1)
+ })
+ }
+
+ private func writeValue(_ newValue: Data, withResponse: Bool = true) {
+
+ let timeout = self.timeout
+ let service = self.service
+ let characteristic = self.characteristic
+ let peripheral = self.service.peripheral
+
+ performActivity({
+ try NativeCentral.shared.connect(to: peripheral, timeout: timeout)
+ defer { NativeCentral.shared.disconnect(peripheral: peripheral) }
+ let _ = try NativeCentral.shared.discoverServices(for: peripheral, timeout: timeout)
+ let _ = try NativeCentral.shared.discoverCharacteristics(for: service, timeout: timeout)
+ try NativeCentral.shared.writeValue(newValue, for: characteristic, withResponse: withResponse, timeout: timeout)
+ }, completion: { (viewController: CharacteristicViewController, _) in
+ viewController.characteristicValue.append(newValue)
+ })
+ }
+
+ private func startNotifications() {
+
+ let timeout = self.timeout
+ let service = self.service
+ let characteristic = self.characteristic
+ let peripheral = self.service.peripheral
+
+ performActivity({
+ try NativeCentral.shared.connect(to: peripheral, timeout: timeout)
+ let _ = try NativeCentral.shared.discoverServices(for: peripheral, timeout: timeout)
+ let _ = try NativeCentral.shared.discoverCharacteristics(for: service, timeout: timeout)
+ try NativeCentral.shared.notify({ [weak self] (newValue) in mainQueue { self?.notification(newValue) } }, for: characteristic)
+ }, completion: { (viewController: CharacteristicViewController, _) in
+ viewController.isNotifying = true
+ })
+ }
+
+ private func notification(_ newValue: Data) {
+
+ NSLog("\(#function) new characteristicValue: \(newValue)")
+ self.characteristicValue.append(newValue)
+ }
+
+ private func stopNotifications() {
+
+ let timeout = self.timeout
+ let service = self.service
+ let characteristic = self.characteristic
+ let peripheral = self.service.peripheral
+
+ performActivity({
+ // should already be connected
+ defer { NativeCentral.shared.disconnect(peripheral: peripheral) }
+ let _ = try NativeCentral.shared.discoverServices(for: peripheral, timeout: timeout)
+ let _ = try NativeCentral.shared.discoverCharacteristics(for: service, timeout: timeout)
+ try NativeCentral.shared.notify(nil, for: characteristic)
+ }, completion: { (viewController: CharacteristicViewController, _) in
+ viewController.isNotifying = false
+ })
+ }
+
+ private func configure(cell: UITableViewCell, with value: String) {
+
+ cell.textLabel?.text = value
+ }
+
+ private func configure(cell: UITableViewCell, with data: Data) {
+
+ cell.textLabel?.text = data.isEmpty ? "No value" : "0x" + data.reduce("", { $0 + String($1, radix: 16) })
+ }
+
+ // MARK: - UITableViewDataSource
+
+ override func numberOfSections(in tableView: UITableView) -> Int {
+ NSLog("count: \(sections.count)")
+ return sections.count
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int {
+
+ let section = self.sections[sectionIndex]
+
+ return section.items.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ #if os(iOS)
+ let item = self[indexPath]
+
+ switch item {
+ case let .uuid(uuid):
+ let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.uuid.rawValue, for: indexPath)
+ configure(cell: cell, with: uuid.rawValue)
+ return cell
+ case let .name(name):
+ let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.name.rawValue, for: indexPath)
+ configure(cell: cell, with: name)
+ return cell
+ case let .value(data):
+ let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.value.rawValue, for: indexPath)
+ configure(cell: cell, with: data)
+ return cell
+ case let .property(property):
+ let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.property.rawValue, for: indexPath)
+ configure(cell: cell, with: property.name)
+ return cell
+ }
+ #else
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
+
+ let layoutName = "list_cell"
+
+ if cell.layoutName != layoutName {
+ cell.inflateAndroidLayout(layoutName: layoutName)
+ }
+
+ let itemView = cell.getItemView()
+
+ let tvItemId = UIApplication.shared.androidActivity.getIdentifier(name: "text_label", type: "id")
+
+ guard let tvItemObject = itemView.findViewById(tvItemId)
+ else { fatalError("No view for \(tvItemId)") }
+
+ let tvItem = Android.Widget.TextView(casting: tvItemObject)
+
+ let item = self.sections[indexPath.section].items[indexPath.row]
+ NSLog("tableView \(indexPath.row) : \(item)")
+ switch item {
+ case let .uuid(uuid):
+ tvItem?.text = uuid.rawValue
+ case let .name(name):
+ tvItem?.text = name
+ case let .value(data):
+ tvItem?.text = data.isEmpty ? "No value" : "0x" + data.reduce("", { $0 + String($1, radix: 16) }).uppercased()
+ case let .property(property):
+ tvItem?.text = property.name
+ }
+ return cell
+ #endif
+ }
+
+ func openAlertForWrite(item: NativeCharacteristic){
+
+ let alertController = UIAlertController.init(title: "Write Characteristic", message: nil, preferredStyle: UIAlertControllerStyle.alert)
+
+ alertController.addTextField(configurationHandler: { text in text.placeholder = "New Value" })
+
+ let action1 = UIAlertAction.init(title: "Write", style: UIAlertActionStyle.default) { action in
+
+ let newValue = alertController.textFields![0].text
+
+ guard let value = newValue, let data = Data(hexString: value) else {
+
+ NSLog("Value is required")
+ //AndroidToast.makeText(context: UIApplication.shared.androidActivity, text: "Value is required", duration: AndroidToast.Dutation.short).show()
+ return
+ }
+
+ self.writeValue(data)
+ }
+
+ let action2 = UIAlertAction.init(title: "Cancel", style: UIAlertActionStyle.cancel)
+
+ alertController.addAction(action1)
+ alertController.addAction(action2)
+
+ present(alertController, animated: false)
+ }
+
+ #if os(iOS)
+ override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ return self.sections[section].title
+ }
+ #else
+ override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+
+ let peripheralViewLayoutId = UIApplication.shared.androidActivity.getIdentifier(name: "list_header", type: "layout")
+
+ let layoutInflarer = Android.View.LayoutInflater.from(context: UIApplication.shared.androidActivity)
+
+ let itemView = layoutInflarer.inflate(resource: Android.R.Layout(rawValue: peripheralViewLayoutId), root: nil, attachToRoot: false)
+
+ let tvHeaderId = UIApplication.shared.androidActivity.getIdentifier(name: "text_label", type: "id")
+
+ guard let tvHeaderObject = itemView.findViewById(tvHeaderId)
+ else { fatalError("No view for \(tvHeaderId)") }
+
+ let tvHeader = Android.Widget.TextView(casting: tvHeaderObject)
+
+ tvHeader?.text = self.sections[section].title
+
+ let uiView = UIView.init(androidViewChild: itemView)
+
+ uiView.androidView.layoutParams = Android.Widget.FrameLayout.FLayoutParams(width: Android.Widget.FrameLayout.FLayoutParams.MATCH_PARENT, height: Android.Widget.FrameLayout.FLayoutParams.WRAP_CONTENT)
+
+ return uiView
+ }
+ #endif
+
+ // MARK: - UITableViewDelegate
+
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ #if os(iOS)
+ defer { tableView.deselectRow(at: indexPath, animated: true) }
+ #endif
+
+ let item = self[indexPath]
+
+ switch item {
+
+ case .uuid,
+ .name:
+ break
+
+ case let .value(data):
+ break // TODO: Show expanded data
+
+ case let .property(property):
+
+ switch property {
+ case .broadcast,
+ .extendedProperties,
+ .signedWrite:
+ break // cant handle
+ case .read:
+ readValue()
+ case .write:
+ openAlertForWrite(item: characteristic)
+ break
+ case .writeWithoutResponse:
+ // TODO: show UI to type new value
+ writeValue(Data(), withResponse: false)
+ break
+ case .notify, .indicate:
+ NSLog("notify")
+ isNotifying ? stopNotifications() : startNotifications()
+ }
+ }
+ }
+}
+
+// MARK: - ActivityIndicatorViewController
+
+extension CharacteristicViewController: ActivityIndicatorViewController {
+
+ func showActivity() {
+
+
+ }
+
+ func hideActivity(animated: Bool = true) {
+
+
+ }
+}
+
+// MARK: - Supporting Types
+
+private extension CharacteristicViewController {
+
+ struct Section {
+
+ let title: String?
+ let items: [Item]
+ }
+
+ enum Item {
+
+ case uuid(BluetoothUUID)
+ case name(String)
+ case value(Data)
+ case property(GATT.CharacteristicProperty)
+ }
+
+ enum CellIdentifier: String {
+
+ case uuid
+ case name
+ case value
+ case property
+ }
+}
+
+
+//FIXME:
+extension Data {
+ init?(hexString: String) {
+ let length = hexString.count / 2
+ var data = Data(capacity: length)
+ for i in 0 ..< length {
+ let j = hexString.index(hexString.startIndex, offsetBy: i * 2)
+ let k = hexString.index(j, offsetBy: 2)
+ let bytes = hexString[j..
+ typealias NativeCharacteristic = Characteristic
+
+ // MARK: - Properties
+
+ let service: NativeService
+
+ private let cellReuseIdentifier = "Cell"
+
+ private let timeout: TimeInterval = .gattDefaultTimeout
+
+ private(set) var items = [NativeCharacteristic]() {
+
+ didSet { self.tableView.reloadData() }
+ }
+
+ // MARK: - Initialization
+
+ init(service: NativeService) {
+
+ self.service = service
+
+ super.init(style: .plain)
+ }
+
+ #if os(iOS)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+ #endif
+
+ // MARK: - Loading
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // setup table view
+ self.tableView.estimatedRowHeight = 44
+ self.tableView.rowHeight = UITableViewAutomaticDimension
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: self.cellReuseIdentifier)
+
+ // add refresh control
+ let refreshControl = UIRefreshControl(frame: .zero)
+
+ #if os(Android) || os(macOS)
+ refreshControl.addTarget(action: { [unowned self] in self.reloadData() }, for: .valueChanged)
+ #else
+ refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
+ #endif
+
+ self.refreshControl = refreshControl
+
+ self.configureView()
+ self.reloadData()
+ }
+
+ // MARK: - Methods
+
+ private subscript (indexPath: IndexPath) -> NativeCharacteristic {
+
+ @inline(__always)
+ get { return self.items[indexPath.row] }
+ }
+
+ #if os(iOS) || os(macOS)
+ @objc func pullToRefresh() {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { [weak self] in
+
+ self?.reloadData()
+ })
+ }
+ #endif
+
+ private func endRefreshing() {
+
+ if let refreshControl = self.refreshControl,
+ refreshControl.isRefreshing == true {
+
+ refreshControl.endRefreshing()
+ }
+ }
+
+ private func configureView() {
+
+ self.title = self.service.uuid.description
+ }
+
+ private func reloadData() {
+
+ let timeout = self.timeout
+
+ let service = self.service
+ let peripheral = self.service.peripheral
+
+ performActivity({
+ try NativeCentral.shared.connect(to: peripheral)
+ defer { NativeCentral.shared.disconnect(peripheral: peripheral) }
+ let _ = try NativeCentral.shared.discoverServices(for: peripheral, timeout: timeout)
+ return try NativeCentral.shared.discoverCharacteristics(for: service, timeout: timeout)
+ }, completion: {
+ $0.items = $1
+ })
+ }
+
+ private func configure(cell: UITableViewCell, at indexPath: IndexPath) {
+
+ let item = self[indexPath]
+
+ cell.textLabel?.text = item.uuid.description
+ }
+
+ // MARK: - UITableViewDataSource
+
+ override func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return items.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
+
+ configure(cell: cell, at: indexPath)
+
+ return cell
+ }
+
+ // MARK: - UITableViewDelegate
+
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ #if os(iOS)
+ defer { tableView.deselectRow(at: indexPath, animated: true) }
+ #endif
+
+ let characteristic = self[indexPath]
+
+ log("Selected \(characteristic.uuid.description)")
+
+ let viewController = CharacteristicViewController(service: service, characteristic: characteristic)
+
+ self.show(viewController, sender: self)
+ }
+}
+
+// MARK: - ActivityIndicatorViewController
+
+extension CharacteristicsViewController: ActivityIndicatorViewController {
+
+ func showActivity() {
+
+
+ }
+
+ func hideActivity(animated: Bool = true) {
+
+ if let refreshControl = self.refreshControl,
+ refreshControl.isRefreshing {
+
+ self.endRefreshing()
+ }
+ }
+}
+
diff --git a/BluetoothExplorer/Controller/Extensions/ErrorAlert.swift b/Android/app/src/main/swift/Sources/ErrorAlert.swift
old mode 100644
new mode 100755
similarity index 94%
rename from BluetoothExplorer/Controller/Extensions/ErrorAlert.swift
rename to Android/app/src/main/swift/Sources/ErrorAlert.swift
index 1fa273c..08ca46e
--- a/BluetoothExplorer/Controller/Extensions/ErrorAlert.swift
+++ b/Android/app/src/main/swift/Sources/ErrorAlert.swift
@@ -7,10 +7,15 @@
//
import Foundation
+#if os(iOS)
import UIKit
+#elseif os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
public extension UIViewController {
-
+
/// Presents an error alert controller with the specified completion handlers.
func showErrorAlert(_ localizedText: String,
okHandler: (() -> ())? = nil,
@@ -42,3 +47,4 @@ public extension UIViewController {
self.present(alert, animated: true, completion: nil)
}
}
+
diff --git a/Android/app/src/main/swift/Sources/Log.swift b/Android/app/src/main/swift/Sources/Log.swift
new file mode 100644
index 0000000..8e9cdad
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/Log.swift
@@ -0,0 +1,19 @@
+//
+// Log.swift
+// BluetoothExplorer
+//
+// Created by Alsey Coleman Miller on 9/7/18.
+// Copyright © 2018 PureSwift. All rights reserved.
+//
+
+import Foundation
+
+/// app logger function
+func log(_ message: String) {
+
+ #if os(Android)
+ NSLog(message)
+ #else
+ print(message)
+ #endif
+}
diff --git a/Android/app/src/main/swift/Sources/MainActivityBindings.swift b/Android/app/src/main/swift/Sources/MainActivityBindings.swift
new file mode 100644
index 0000000..502eb65
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/MainActivityBindings.swift
@@ -0,0 +1,18 @@
+
+import java_swift
+
+/// generated by: genswift.java 'java/lang|java/util|java/sql' 'Sources' '../java' ///
+
+/// interface com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings ///
+
+public protocol MainActivityBindings: JavaProtocol {
+
+}
+
+
+open class MainActivityBindingsForward: JNIObjectForward, MainActivityBindings {
+
+ private static var MainActivityBindingsJNIClass: jclass?
+
+}
+
diff --git a/Android/app/src/main/swift/Sources/MainActivityBindings_Listener.swift b/Android/app/src/main/swift/Sources/MainActivityBindings_Listener.swift
new file mode 100644
index 0000000..7b43869
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/MainActivityBindings_Listener.swift
@@ -0,0 +1,85 @@
+
+import java_swift
+
+/// generated by: genswift.java 'java/lang|java/util|java/sql' 'Sources' '../java' ///
+
+/// interface com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings$Listener ///
+
+public protocol MainActivityBindings_Listener: JavaProtocol {
+
+ /// public abstract void com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings$Listener.hi()
+
+ func hi()
+
+}
+
+
+open class MainActivityBindings_ListenerForward: JNIObjectForward, MainActivityBindings_Listener {
+
+ private static var MainActivityBindings_ListenerJNIClass: jclass?
+
+ /// public abstract void com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings$Listener.hi()
+
+ private static var hi_MethodID_2: jmethodID?
+
+ open func hi() {
+ var __locals = [jobject]()
+ var __args = [jvalue]( repeating: jvalue(), count: 1 )
+ JNIMethod.CallVoidMethod( object: javaObject, methodName: "hi", methodSig: "()V", methodCache: &MainActivityBindings_ListenerForward.hi_MethodID_2, args: &__args, locals: &__locals )
+ }
+
+
+}
+
+private typealias MainActivityBindings_Listener_hi_0_type = @convention(c) ( _: UnsafeMutablePointer, _: jobject?, _: jlong ) -> ()
+
+private func MainActivityBindings_Listener_hi_0( _ __env: UnsafeMutablePointer, _ __this: jobject?, _ __swiftObject: jlong ) -> () {
+ MainActivityBindings_ListenerLocal_.swiftObject( jniEnv: __env, javaObject: __this, swiftObject: __swiftObject ).hi( )
+}
+
+fileprivate class MainActivityBindings_ListenerLocal_: JNILocalProxy {
+
+ fileprivate static let _proxyClass: jclass = {
+ var natives = [JNINativeMethod]()
+
+ let MainActivityBindings_Listener_hi_0_thunk: MainActivityBindings_Listener_hi_0_type = MainActivityBindings_Listener_hi_0
+ natives.append( JNINativeMethod( name: strdup("__hi"), signature: strdup("(J)V"), fnPtr: unsafeBitCast( MainActivityBindings_Listener_hi_0_thunk, to: UnsafeMutableRawPointer.self ) ) )
+
+ natives.append( JNINativeMethod( name: strdup("__finalize"), signature: strdup("(J)V"), fnPtr: unsafeBitCast( JNIReleasableProxy__finalize_thunk, to: UnsafeMutableRawPointer.self ) ) )
+
+ let clazz = JNI.FindClass( proxyClassName() )
+ natives.withUnsafeBufferPointer {
+ nativesPtr in
+ if JNI.api.RegisterNatives( JNI.env, clazz, nativesPtr.baseAddress, jint(nativesPtr.count) ) != jint(JNI_OK) {
+ JNI.report( "Unable to register java natives" )
+ }
+ }
+
+ defer { JNI.DeleteLocalRef( clazz ) }
+ return JNI.api.NewGlobalRef( JNI.env, clazz )!
+ }()
+
+ override open class func proxyClassName() -> String { return "org/swiftjava/com_millertech/MainActivityBindings_ListenerProxy" }
+ override open class func proxyClass() -> jclass? { return _proxyClass }
+
+}
+
+extension MainActivityBindings_Listener {
+
+ public func localJavaObject( _ locals: UnsafeMutablePointer<[jobject]> ) -> jobject? {
+ return MainActivityBindings_ListenerLocal_( owned: self, proto: self ).localJavaObject( locals )
+ }
+
+}
+
+open class MainActivityBindings_ListenerBase: MainActivityBindings_Listener {
+
+ public init() {}
+
+ /// public abstract void com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings$Listener.hi()
+
+ open func hi() /**/ {
+ }
+
+
+}
diff --git a/Android/app/src/main/swift/Sources/MainActivityBindings_Responder.swift b/Android/app/src/main/swift/Sources/MainActivityBindings_Responder.swift
new file mode 100644
index 0000000..10402a5
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/MainActivityBindings_Responder.swift
@@ -0,0 +1,18 @@
+
+import java_swift
+
+/// generated by: genswift.java 'java/lang|java/util|java/sql' 'Sources' '../java' ///
+
+/// interface com.millertech.bluetoothexplorer.swiftbindings.MainActivityBindings$Responder ///
+
+public protocol MainActivityBindings_Responder: JavaProtocol {
+
+}
+
+
+open class MainActivityBindings_ResponderForward: JNIObjectForward, MainActivityBindings_Responder {
+
+ private static var MainActivityBindings_ResponderJNIClass: jclass?
+
+}
+
diff --git a/Android/app/src/main/swift/Sources/ServicesViewController.swift b/Android/app/src/main/swift/Sources/ServicesViewController.swift
new file mode 100644
index 0000000..41803d8
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/ServicesViewController.swift
@@ -0,0 +1,195 @@
+//
+// ServicesViewController.swift
+// BluetoothExplorer
+//
+// Created by Alsey Coleman Miller on 9/7/18.
+// Copyright © 2018 PureSwift. All rights reserved.
+//
+
+import Foundation
+import Bluetooth
+import GATT
+
+#if os(iOS)
+import UIKit
+#elseif os(Android) || os(macOS)
+import Android
+import AndroidUIKit
+#endif
+
+/// Services
+final class ServicesViewController: UITableViewController {
+
+ typealias NativeScanData = ScanData
+
+ typealias NativeService = Service
+
+ // MARK: - Properties
+
+ let scanData: NativeScanData
+
+ private(set) var items = [NativeService]() {
+
+ didSet { self.tableView.reloadData() }
+ }
+
+ private let cellReuseIdentifier = "Cell"
+
+ private let timeout: TimeInterval = .gattDefaultTimeout
+
+ // MARK: - Loading
+
+ init(scanData: NativeScanData) {
+
+ self.scanData = scanData
+
+ super.init(style: .plain)
+ }
+
+ #if os(iOS)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+ #endif
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // setup table view
+ self.tableView.estimatedRowHeight = 44
+ self.tableView.rowHeight = UITableViewAutomaticDimension
+ self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: self.cellReuseIdentifier)
+
+ // add refresh control
+
+ let actionRefresh: () -> () = {
+
+ self.reloadData()
+ }
+
+ let refreshControl = UIRefreshControl(frame: .zero)
+
+ #if os(Android) || os(macOS)
+ refreshControl.addTarget(action: actionRefresh, for: UIControlEvents.valueChanged)
+ #else
+ refreshControl.addTarget(self, action: #selector(pullToRefresh), for: UIControlEvents.valueChanged)
+ #endif
+
+ self.refreshControl = refreshControl
+
+ self.configureView()
+ self.reloadData()
+ }
+
+ // MARK: - Actions
+
+ #if os(iOS) || os(macOS)
+ @objc func pullToRefresh() {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { [weak self] in
+
+ self?.reloadData()
+ })
+ }
+ #endif
+
+ // MARK: - Methods
+
+ private subscript (indexPath: IndexPath) -> NativeService {
+
+ @inline(__always)
+ get { return self.items[indexPath.row] }
+ }
+
+ private func configureView() {
+
+ self.title = self.scanData.advertisementData.localName ?? self.scanData.peripheral.identifier.description
+ }
+
+ private func reloadData() {
+
+ let peripheral = self.scanData.peripheral
+ let timeout = self.timeout
+
+ performActivity({
+ try NativeCentral.shared.connect(to: peripheral)
+ defer { NativeCentral.shared.disconnect(peripheral: peripheral) }
+ return try NativeCentral.shared.discoverServices([], for: peripheral, timeout: timeout)
+ }, completion: {
+ $0.items = $1
+ })
+ }
+
+ private func endRefreshing() {
+
+ if let refreshControl = self.refreshControl,
+ refreshControl.isRefreshing == true {
+
+ refreshControl.endRefreshing()
+ }
+ }
+
+ private func configure(cell: UITableViewCell, at indexPath: IndexPath) {
+
+ let item = self[indexPath]
+
+ cell.textLabel?.text = item.uuid.description
+ }
+
+ // MARK: - UITableViewDataSource
+
+ override func numberOfSections(in tableView: UITableView) -> Int {
+
+ return 1
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return items.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
+
+ configure(cell: cell, at: indexPath)
+
+ return cell
+ }
+
+ // MARK: - UITableViewDelegate
+
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ #if os(iOS)
+ defer { tableView.deselectRow(at: indexPath, animated: true) }
+ #endif
+
+ let item = self[indexPath]
+
+ log("Selected \(item.uuid.description)")
+
+ let viewController = CharacteristicsViewController(service: item)
+
+ self.show(viewController, sender: self)
+ }
+}
+
+// MARK: - ActivityIndicatorViewController
+
+extension ServicesViewController: ActivityIndicatorViewController {
+
+ func showActivity() {
+
+
+ }
+
+ func hideActivity(animated: Bool = true) {
+
+ if let refreshControl = self.refreshControl,
+ refreshControl.isRefreshing {
+
+ self.endRefreshing()
+ }
+ }
+}
diff --git a/Android/app/src/main/swift/Sources/main.swift b/Android/app/src/main/swift/Sources/main.swift
new file mode 100644
index 0000000..00a96fd
--- /dev/null
+++ b/Android/app/src/main/swift/Sources/main.swift
@@ -0,0 +1,44 @@
+//
+// main.swift
+// AndroidUIKit
+//
+// Created by Marco Estrella on 9/6/18.
+//
+
+import Foundation
+
+#if os(iOS)
+
+import UIKit
+
+UIApplicationMain(0, nil, nil, NSStringFromClass(AppDelegate.self))
+
+#else
+
+import Android
+import AndroidUIKit
+
+/// Needs to be implemented by app.
+@_silgen_name("SwiftAndroidMainApplication")
+public func SwiftAndroidMainApplication() -> SwiftApplication.Type {
+
+ NSLog("\(#function)")
+
+ // initialize singleton App Delegate
+ UIApplication.shared.delegate = AppDelegate()
+
+ // return specialized Android Application
+ return AndroidUIKitApplication.self
+}
+
+/// Needs to be implemented by app.
+@_silgen_name("SwiftAndroidMainActivity")
+public func SwiftAndroidMainActivity() -> SwiftSupportAppCompatActivity.Type {
+
+ NSLog("\(#function)")
+
+ // return specialized Android Activity
+ return AndroidUIKitMainActivity.self
+}
+
+#endif
diff --git a/Android/app/src/test/java/com/millertech/bluetoothexplorer/ExampleUnitTest.kt b/Android/app/src/test/java/com/millertech/bluetoothexplorer/ExampleUnitTest.kt
new file mode 100644
index 0000000..a89df4a
--- /dev/null
+++ b/Android/app/src/test/java/com/millertech/bluetoothexplorer/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.millertech.bluetoothexplorer
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/Android/build.gradle b/Android/build.gradle
new file mode 100644
index 0000000..fadcee6
--- /dev/null
+++ b/Android/build.gradle
@@ -0,0 +1,32 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext.kotlin_version = '1.2.51'
+ repositories {
+ google()
+ jcenter()
+ mavenLocal()
+ mavenCentral()
+ maven { url 'https://jitpack.io' }
+ }
+ dependencies {
+ classpath 'net.zhuoweizhang:swiftandroid:1.0.0'
+ classpath 'com.android.tools.build:gradle:3.1.4'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ maven { url 'https://jitpack.io' }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/Android/gradle.properties b/Android/gradle.properties
new file mode 100644
index 0000000..743d692
--- /dev/null
+++ b/Android/gradle.properties
@@ -0,0 +1,13 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/Android/gradle/wrapper/gradle-wrapper.jar b/Android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7a3265e
Binary files /dev/null and b/Android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Android/gradle/wrapper/gradle-wrapper.properties b/Android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d5b3c01
--- /dev/null
+++ b/Android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Sep 05 14:57:00 PET 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/Android/gradlew b/Android/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/Android/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/Android/gradlew.bat b/Android/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/Android/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Android/settings.gradle b/Android/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/Android/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/BluetoothExplorer.xcodeproj/project.pbxproj b/BluetoothExplorer.xcodeproj/project.pbxproj
deleted file mode 100644
index d2bf8f0..0000000
--- a/BluetoothExplorer.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,1184 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 46;
- objects = {
-
-/* Begin PBXBuildFile section */
- 35298F1A20EACCB10093B830 /* BatteryPowerStateCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35298F1920EACCB10093B830 /* BatteryPowerStateCharacteristic.storyboard */; };
- 35298F2620EACD700093B830 /* PnPIDCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35298F2520EACD700093B830 /* PnPIDCharacteristicViewController.swift */; };
- 35298F4920EAE1D60093B830 /* AlertLevelCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35298F4820EAE1D60093B830 /* AlertLevelCharacteristicViewController.swift */; };
- 35298F5B20EB08D10093B830 /* AlertLevelCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35298F5A20EB08D10093B830 /* AlertLevelCharacteristic.storyboard */; };
- 35317A2E20E53BD500D31493 /* HardwareRevisionStringCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35317A2D20E53BD500D31493 /* HardwareRevisionStringCharacteristic.storyboard */; };
- 35317A3720E53C2C00D31493 /* HardwareRevisionStringCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35317A3620E53C2C00D31493 /* HardwareRevisionStringCharacteristicViewController.swift */; };
- 35317A3920E5485400D31493 /* SerialNumberStringCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35317A3820E5485400D31493 /* SerialNumberStringCharacteristic.storyboard */; };
- 35317A3B20E5486200D31493 /* SerialNumberStringCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35317A3A20E5486200D31493 /* SerialNumberStringCharacteristicViewController.swift */; };
- 35317A3F20E56B9800D31493 /* AlertCategoryCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35317A3E20E56B9800D31493 /* AlertCategoryCharacteristic.storyboard */; };
- 35317A4820E56CCA00D31493 /* AlertCategoryCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35317A4720E56CCA00D31493 /* AlertCategoryCharacteristicViewController.swift */; };
- 35335A0520E1316F006ABD4D /* ModelNumberCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35335A0420E1316F006ABD4D /* ModelNumberCharacteristic.storyboard */; };
- 35335A0720E13203006ABD4D /* ModelNumberCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35335A0620E13203006ABD4D /* ModelNumberCharacteristicViewController.swift */; };
- 35335A1220E13FC0006ABD4D /* FirmwareRevisionStringCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35335A0A20E13FC0006ABD4D /* FirmwareRevisionStringCharacteristic.storyboard */; };
- 35335A1420E13FF0006ABD4D /* FirmwareRevisionStringCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35335A1320E13FF0006ABD4D /* FirmwareRevisionStringCharacteristicViewController.swift */; };
- 35335A1620E14353006ABD4D /* SoftwareRevisionStringCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35335A1520E14353006ABD4D /* SoftwareRevisionStringCharacteristic.storyboard */; };
- 35335A1820E14392006ABD4D /* SoftwareRevisionStringCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35335A1720E14392006ABD4D /* SoftwareRevisionStringCharacteristicViewController.swift */; };
- 35335A1A20E14830006ABD4D /* ManufacturerNameStringCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35335A1920E14830006ABD4D /* ManufacturerNameStringCharacteristic.storyboard */; };
- 35335A1C20E14846006ABD4D /* ManufacturerNameStringCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35335A1B20E14846006ABD4D /* ManufacturerNameStringCharacteristicViewController.swift */; };
- 35335A1E20E14EFA006ABD4D /* SystemIDCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35335A1D20E14EFA006ABD4D /* SystemIDCharacteristic.storyboard */; };
- 35335A2B20E16D4F006ABD4D /* DateTimeCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35335A2320E16D4F006ABD4D /* DateTimeCharacteristic.storyboard */; };
- 35335A2D20E16D74006ABD4D /* DateTimeCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35335A2C20E16D74006ABD4D /* DateTimeCharacteristicViewController.swift */; };
- 353557C720E28CB700543440 /* InstantiableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353557C620E28CB700543440 /* InstantiableViewController.swift */; };
- 353557D020E28CD200543440 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353557CF20E28CD200543440 /* String.swift */; };
- 353557D620E2D15000543440 /* PnPIDCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 353557D520E2D15000543440 /* PnPIDCharacteristic.storyboard */; };
- 353557DF20E2D17300543440 /* BatteryPowerStateCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353557DE20E2D17300543440 /* BatteryPowerStateCharacteristicViewController.swift */; };
- 35386B0220EBC5000016D662 /* AlertNotificationControlPointCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35386B0120EBC5000016D662 /* AlertNotificationControlPointCharacteristic.storyboard */; };
- 35386B0E20EBC5920016D662 /* AlertNotificationControlPointCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35386B0D20EBC5920016D662 /* AlertNotificationControlPointCharacteristicViewController.swift */; };
- 35386B1520EBE13E0016D662 /* AgeCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35386B1420EBE13E0016D662 /* AgeCharacteristic.storyboard */; };
- 35386B2120EBE1530016D662 /* AgeCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35386B2020EBE1530016D662 /* AgeCharacteristicViewController.swift */; };
- 35386B3820EBF0310016D662 /* AerobicHeartRateLowerLimitCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35386B3720EBF0310016D662 /* AerobicHeartRateLowerLimitCharacteristicViewController.swift */; };
- 35386B4420EBF64A0016D662 /* AerobicHeartRateLowerLimitCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35386B4320EBF64A0016D662 /* AerobicHeartRateLowerLimitCharacteristic.storyboard */; };
- 35386B4620EBFBC50016D662 /* AerobicHeartRateUpperLimitCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35386B4520EBFBC50016D662 /* AerobicHeartRateUpperLimitCharacteristicViewController.swift */; };
- 35386B4820EBFC1B0016D662 /* AerobicHeartRateUpperLimitCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35386B4720EBFC1B0016D662 /* AerobicHeartRateUpperLimitCharacteristic.storyboard */; };
- 35CA5E0920E5740E00F6B589 /* Integer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35CA5E0820E5740E00F6B589 /* Integer.swift */; };
- 35CA5E1220E5777000F6B589 /* GATTAlertCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35CA5E1120E5777000F6B589 /* GATTAlertCategory.swift */; };
- 35CA5E1520E579B100F6B589 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 35CA5E1720E579B100F6B589 /* Localizable.strings */; };
- 35CA5E3D20E58F5400F6B589 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35CA5E3C20E58F5300F6B589 /* R.generated.swift */; };
- 35F5BB5A20E3DAC500AC5FF1 /* InputTextView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35F5BB5920E3DAC500AC5FF1 /* InputTextView.xib */; };
- 35F5BB6320E3DB8A00AC5FF1 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F5BB6220E3DB8A00AC5FF1 /* InputTextView.swift */; };
- 35F5BB6520E3DC0000AC5FF1 /* NibDesignable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F5BB6420E3DC0000AC5FF1 /* NibDesignable.swift */; };
- 35FE560120E41F5B0052C0C0 /* InputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE560020E41F5B0052C0C0 /* InputTextField.swift */; };
- 35FE560C20E434860052C0C0 /* SystemIDCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE560B20E434860052C0C0 /* SystemIDCharacteristicViewController.swift */; };
- 35FE560E20E435860052C0C0 /* InputTextViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 35FE560D20E435860052C0C0 /* InputTextViewCell.xib */; };
- 35FE561020E436CA0052C0C0 /* InputTextViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE560F20E436CA0052C0C0 /* InputTextViewCell.swift */; };
- 6E10553520E9CDFA00D0CD0D /* Rswift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 35CA5E2520E5865300F6B589 /* Rswift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 6E13E8CC20D9C7270007111B /* AdvertisementDataManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E13E8CB20D9C7270007111B /* AdvertisementDataManagedObject.swift */; };
- 6E13E8E320D9D8290007111B /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E13E8E220D9D8290007111B /* TableViewController.swift */; };
- 6E353A4620D7FDB200E94B73 /* CoreDataEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E353A4520D7FDB200E94B73 /* CoreDataEncodable.swift */; };
- 6E353A4C20D7FDC900E94B73 /* CoreDataDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E353A4B20D7FDC900E94B73 /* CoreDataDecodable.swift */; };
- 6E8C68E520DA02B600232169 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8C68E420DA02B600232169 /* ErrorAlert.swift */; };
- 6E8C68E920DA02DA00232169 /* PresentPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8C68E620DA02DA00232169 /* PresentPopover.swift */; };
- 6E8C68EA20DA02DA00232169 /* UIAlertAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8C68E720DA02DA00232169 /* UIAlertAction.swift */; };
- 6E8C68EB20DA02DA00232169 /* AdaptiveNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8C68E820DA02DA00232169 /* AdaptiveNavigation.swift */; };
- 6E8C68ED20DA030A00232169 /* ActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8C68EC20DA030900232169 /* ActivityIndicatorViewController.swift */; };
- 6E8C68F020DA04D500232169 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8C68EF20DA04D400232169 /* Async.swift */; };
- 6E8C68F220DA340C00232169 /* PeripheralServicesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8C68F120DA340C00232169 /* PeripheralServicesViewController.swift */; };
- 6E94B2AF20DAC53200404FD9 /* PeripheralCharacteristicsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E94B2AE20DAC53200404FD9 /* PeripheralCharacteristicsViewController.swift */; };
- 6E98B6962059B51000B6F016 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E98B6952059B51000B6F016 /* AppDelegate.swift */; };
- 6E98B6982059B51000B6F016 /* PeripheralsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E98B6972059B51000B6F016 /* PeripheralsViewController.swift */; };
- 6E98B69B2059B51000B6F016 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E98B6992059B51000B6F016 /* Main.storyboard */; };
- 6E98B69D2059B51000B6F016 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6E98B69C2059B51000B6F016 /* Assets.xcassets */; };
- 6E98B6A02059B51000B6F016 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E98B69E2059B51000B6F016 /* LaunchScreen.storyboard */; };
- 6EC0D9BE20DDF871005FB128 /* Bluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC0D9A120DDF5E0005FB128 /* Bluetooth.framework */; };
- 6EC0D9BF20DDF871005FB128 /* Bluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC0D9A120DDF5E0005FB128 /* Bluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 6EC0D9C220DDF876005FB128 /* GATT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC0D9B320DDF5E8005FB128 /* GATT.framework */; };
- 6EC0D9C320DDF876005FB128 /* GATT.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC0D9B320DDF5E8005FB128 /* GATT.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 6EC0D9C720DE076A005FB128 /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0D9C620DE076A005FB128 /* Appearance.swift */; };
- 6ECCCC6C20E091070054663C /* FDTransformLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC5D20E091060054663C /* FDTransformLayer.m */; };
- 6ECCCC6D20E091070054663C /* FDLayoutSpacer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC5E20E091060054663C /* FDLayoutSpacer.m */; };
- 6ECCCC6E20E091070054663C /* FDStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC6120E091060054663C /* FDStackView.m */; };
- 6ECCCC6F20E091070054663C /* FDStackViewExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC6320E091060054663C /* FDStackViewExtensions.m */; };
- 6ECCCC7020E091070054663C /* FDStackViewAlignmentLayoutArrangement.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC6520E091060054663C /* FDStackViewAlignmentLayoutArrangement.m */; };
- 6ECCCC7120E091070054663C /* FDStackViewDistributionLayoutArrangement.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC6620E091060054663C /* FDStackViewDistributionLayoutArrangement.m */; };
- 6ECCCC7220E091070054663C /* FDStackViewLayoutArrangement.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC6820E091060054663C /* FDStackViewLayoutArrangement.m */; };
- 6ECCCC7320E091070054663C /* FDGapLayoutGuide.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ECCCC6920E091060054663C /* FDGapLayoutGuide.m */; };
- 6EDE00C620DB0D4F002E951A /* PeripheralCharacteristicDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE00C520DB0D4F002E951A /* PeripheralCharacteristicDetailViewController.swift */; };
- 6EDE00C920DB0EB5002E951A /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE00C820DB0EB5002E951A /* Data.swift */; };
- 6EDE00CF20DB12B3002E951A /* BatteryLevelCharacteristic.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6EDE00CE20DB12B3002E951A /* BatteryLevelCharacteristic.storyboard */; };
- 6EDE00D220DB47FD002E951A /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE00D120DB47FD002E951A /* CustomButton.swift */; };
- 6EDE00D520DB53B4002E951A /* BatteryLevelCharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE00D420DB4C84002E951A /* BatteryLevelCharacteristicViewController.swift */; };
- 6EDE00D720DB60D9002E951A /* CharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE00D620DB60D9002E951A /* CharacteristicViewController.swift */; };
- 6EFB3E2520D3E5A600364CA0 /* DeviceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB3E2420D3E5A600364CA0 /* DeviceStore.swift */; };
- 6EFB3E8D20D3ECF200364CA0 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB3E8B20D3ECF200364CA0 /* Model.xcdatamodeld */; };
- 6EFB3E9120D3EF6C00364CA0 /* CoreDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB3E9020D3EF6C00364CA0 /* CoreDataExtensions.swift */; };
- 6EFB3E9320D3EF9A00364CA0 /* ScanDataManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB3E9220D3EF9A00364CA0 /* ScanDataManagedObject.swift */; };
- 6EFB3E9520D3EFFB00364CA0 /* PeripheralManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB3E9420D3EFFB00364CA0 /* PeripheralManagedObject.swift */; };
- 6EFB3E9720D3F0D700364CA0 /* CentralManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB3E9620D3F0D700364CA0 /* CentralManagedObject.swift */; };
- 6EFB3E9B20D40F2000364CA0 /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB3E9A20D40F2000364CA0 /* PeripheralModel.swift */; };
- 6EFD557E20D83B8F003B75A1 /* ServiceManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD557D20D83B8F003B75A1 /* ServiceManagedObject.swift */; };
- 6EFD558420D83C76003B75A1 /* CharacteristicManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD558320D83C76003B75A1 /* CharacteristicManagedObject.swift */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 35CA5E2420E5865300F6B589 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = D592464E1C117A55007F94C7;
- remoteInfo = "Rswift-iOS";
- };
- 35CA5E2620E5865300F6B589 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = D59246581C117A55007F94C7;
- remoteInfo = "RswiftTests-iOS";
- };
- 35CA5E2820E5865300F6B589 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 806E69921C42BD9C00DE3A8B;
- remoteInfo = "Rswift-tvOS";
- };
- 35CA5E2A20E5865300F6B589 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 806E699B1C42BD9C00DE3A8B;
- remoteInfo = "RswiftTests-tvOS";
- };
- 6E10553320E9CDEF00D0CD0D /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = D592464D1C117A55007F94C7;
- remoteInfo = "Rswift-iOS";
- };
- 6EC0D99E20DDF5E0005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6EE84DB71CAF5C7C00A40C4D;
- remoteInfo = "Bluetooth-macOS";
- };
- 6EC0D9A020DDF5E0005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6EF45FBD1CC6D04D001F7A39;
- remoteInfo = "Bluetooth-iOS";
- };
- 6EC0D9A220DDF5E0005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6EB2EA1D1CD5A8A7000CF975;
- remoteInfo = "Bluetooth-watchOS";
- };
- 6EC0D9A420DDF5E0005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6E49B26520532D45002EA5DC;
- remoteInfo = "Bluetooth-tvOS";
- };
- 6EC0D9A620DDF5E0005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6EE84DC11CAF5C7C00A40C4D;
- remoteInfo = BluetoothTests;
- };
- 6EC0D9B020DDF5E8005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6EE84D841CAF419D00A40C4D;
- remoteInfo = "GATT-macOS";
- };
- 6EC0D9B220DDF5E8005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6EF45FCF1CC6D355001F7A39;
- remoteInfo = "GATT-iOS";
- };
- 6EC0D9B420DDF5E8005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6EE9103A1FDE5C17007AD3EA;
- remoteInfo = "GATT-watchOS";
- };
- 6EC0D9B620DDF5E8005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 6E49B23A20532A94002EA5DC;
- remoteInfo = "GATT-tvOS";
- };
- 6EC0D9BA20DDF5F6005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = 6EF45FBF1CC6D355001F7A39;
- remoteInfo = "GATT-iOS";
- };
- 6EC0D9BC20DDF5F6005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = 6EF45FA21CC6D04D001F7A39;
- remoteInfo = "Bluetooth-iOS";
- };
- 6EC0D9C020DDF871005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = 6EF45FA21CC6D04D001F7A39;
- remoteInfo = "Bluetooth-iOS";
- };
- 6EC0D9C420DDF877005FB128 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = 6EF45FBF1CC6D355001F7A39;
- remoteInfo = "GATT-iOS";
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 6EFB3E2C20D3E89200364CA0 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- 6E10553520E9CDFA00D0CD0D /* Rswift.framework in Embed Frameworks */,
- 6EC0D9C320DDF876005FB128 /* GATT.framework in Embed Frameworks */,
- 6EC0D9BF20DDF871005FB128 /* Bluetooth.framework in Embed Frameworks */,
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 35298F1920EACCB10093B830 /* BatteryPowerStateCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BatteryPowerStateCharacteristic.storyboard; sourceTree = ""; };
- 35298F2520EACD700093B830 /* PnPIDCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PnPIDCharacteristicViewController.swift; sourceTree = ""; };
- 35298F4820EAE1D60093B830 /* AlertLevelCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertLevelCharacteristicViewController.swift; sourceTree = ""; };
- 35298F5A20EB08D10093B830 /* AlertLevelCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AlertLevelCharacteristic.storyboard; sourceTree = ""; };
- 35317A2D20E53BD500D31493 /* HardwareRevisionStringCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = HardwareRevisionStringCharacteristic.storyboard; sourceTree = ""; };
- 35317A3620E53C2C00D31493 /* HardwareRevisionStringCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareRevisionStringCharacteristicViewController.swift; sourceTree = ""; };
- 35317A3820E5485400D31493 /* SerialNumberStringCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SerialNumberStringCharacteristic.storyboard; sourceTree = ""; };
- 35317A3A20E5486200D31493 /* SerialNumberStringCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialNumberStringCharacteristicViewController.swift; sourceTree = ""; };
- 35317A3E20E56B9800D31493 /* AlertCategoryCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AlertCategoryCharacteristic.storyboard; sourceTree = ""; };
- 35317A4720E56CCA00D31493 /* AlertCategoryCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCategoryCharacteristicViewController.swift; sourceTree = ""; };
- 35335A0420E1316F006ABD4D /* ModelNumberCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ModelNumberCharacteristic.storyboard; sourceTree = ""; };
- 35335A0620E13203006ABD4D /* ModelNumberCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelNumberCharacteristicViewController.swift; sourceTree = ""; };
- 35335A0A20E13FC0006ABD4D /* FirmwareRevisionStringCharacteristic.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = FirmwareRevisionStringCharacteristic.storyboard; sourceTree = ""; };
- 35335A1320E13FF0006ABD4D /* FirmwareRevisionStringCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareRevisionStringCharacteristicViewController.swift; sourceTree = ""; };
- 35335A1520E14353006ABD4D /* SoftwareRevisionStringCharacteristic.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SoftwareRevisionStringCharacteristic.storyboard; sourceTree = ""; };
- 35335A1720E14392006ABD4D /* SoftwareRevisionStringCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareRevisionStringCharacteristicViewController.swift; sourceTree = ""; };
- 35335A1920E14830006ABD4D /* ManufacturerNameStringCharacteristic.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ManufacturerNameStringCharacteristic.storyboard; sourceTree = ""; };
- 35335A1B20E14846006ABD4D /* ManufacturerNameStringCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManufacturerNameStringCharacteristicViewController.swift; sourceTree = ""; };
- 35335A1D20E14EFA006ABD4D /* SystemIDCharacteristic.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SystemIDCharacteristic.storyboard; sourceTree = ""; };
- 35335A2320E16D4F006ABD4D /* DateTimeCharacteristic.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DateTimeCharacteristic.storyboard; sourceTree = ""; };
- 35335A2C20E16D74006ABD4D /* DateTimeCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeCharacteristicViewController.swift; sourceTree = ""; };
- 353557C620E28CB700543440 /* InstantiableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantiableViewController.swift; sourceTree = ""; };
- 353557CF20E28CD200543440 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; };
- 353557D520E2D15000543440 /* PnPIDCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PnPIDCharacteristic.storyboard; sourceTree = ""; };
- 353557DE20E2D17300543440 /* BatteryPowerStateCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryPowerStateCharacteristicViewController.swift; sourceTree = ""; };
- 35386B0120EBC5000016D662 /* AlertNotificationControlPointCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AlertNotificationControlPointCharacteristic.storyboard; sourceTree = ""; };
- 35386B0D20EBC5920016D662 /* AlertNotificationControlPointCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertNotificationControlPointCharacteristicViewController.swift; sourceTree = ""; };
- 35386B1420EBE13E0016D662 /* AgeCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AgeCharacteristic.storyboard; sourceTree = ""; };
- 35386B2020EBE1530016D662 /* AgeCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeCharacteristicViewController.swift; sourceTree = ""; };
- 35386B3720EBF0310016D662 /* AerobicHeartRateLowerLimitCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AerobicHeartRateLowerLimitCharacteristicViewController.swift; sourceTree = ""; };
- 35386B4320EBF64A0016D662 /* AerobicHeartRateLowerLimitCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AerobicHeartRateLowerLimitCharacteristic.storyboard; sourceTree = ""; };
- 35386B4520EBFBC50016D662 /* AerobicHeartRateUpperLimitCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AerobicHeartRateUpperLimitCharacteristicViewController.swift; sourceTree = ""; };
- 35386B4720EBFC1B0016D662 /* AerobicHeartRateUpperLimitCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AerobicHeartRateUpperLimitCharacteristic.storyboard; sourceTree = ""; };
- 35CA5E0820E5740E00F6B589 /* Integer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Integer.swift; sourceTree = ""; };
- 35CA5E1120E5777000F6B589 /* GATTAlertCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GATTAlertCategory.swift; sourceTree = ""; };
- 35CA5E1620E579B100F6B589 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
- 35CA5E1A20E584A100F6B589 /* Rswift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Rswift.framework; path = Carthage/Build/iOS/Rswift.framework; sourceTree = ""; };
- 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = R.swift.Library.xcodeproj; path = Carthage/Checkouts/R.swift.Library/R.swift.Library.xcodeproj; sourceTree = ""; };
- 35CA5E3C20E58F5300F6B589 /* R.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = R.generated.swift; sourceTree = ""; };
- 35F5BB5920E3DAC500AC5FF1 /* InputTextView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InputTextView.xib; sourceTree = ""; };
- 35F5BB6220E3DB8A00AC5FF1 /* InputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = ""; };
- 35F5BB6420E3DC0000AC5FF1 /* NibDesignable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibDesignable.swift; sourceTree = ""; };
- 35FE560020E41F5B0052C0C0 /* InputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextField.swift; sourceTree = ""; };
- 35FE560B20E434860052C0C0 /* SystemIDCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemIDCharacteristicViewController.swift; sourceTree = ""; };
- 35FE560D20E435860052C0C0 /* InputTextViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InputTextViewCell.xib; sourceTree = ""; };
- 35FE560F20E436CA0052C0C0 /* InputTextViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextViewCell.swift; sourceTree = ""; };
- 6E13E8CB20D9C7270007111B /* AdvertisementDataManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvertisementDataManagedObject.swift; sourceTree = ""; };
- 6E13E8E220D9D8290007111B /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; };
- 6E353A4520D7FDB200E94B73 /* CoreDataEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataEncodable.swift; sourceTree = ""; };
- 6E353A4B20D7FDC900E94B73 /* CoreDataDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataDecodable.swift; sourceTree = ""; };
- 6E8C68E420DA02B600232169 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; };
- 6E8C68E620DA02DA00232169 /* PresentPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentPopover.swift; sourceTree = ""; };
- 6E8C68E720DA02DA00232169 /* UIAlertAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAlertAction.swift; sourceTree = ""; };
- 6E8C68E820DA02DA00232169 /* AdaptiveNavigation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveNavigation.swift; sourceTree = ""; };
- 6E8C68EC20DA030900232169 /* ActivityIndicatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorViewController.swift; sourceTree = ""; };
- 6E8C68EF20DA04D400232169 /* Async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Async.swift; sourceTree = ""; };
- 6E8C68F120DA340C00232169 /* PeripheralServicesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralServicesViewController.swift; sourceTree = ""; };
- 6E94B2AE20DAC53200404FD9 /* PeripheralCharacteristicsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralCharacteristicsViewController.swift; sourceTree = ""; };
- 6E98B6932059B51000B6F016 /* BluetoothExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BluetoothExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 6E98B6952059B51000B6F016 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 6E98B6972059B51000B6F016 /* PeripheralsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralsViewController.swift; sourceTree = ""; };
- 6E98B69A2059B51000B6F016 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- 6E98B69C2059B51000B6F016 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 6E98B69F2059B51000B6F016 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 6E98B6A12059B51000B6F016 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Bluetooth.xcodeproj; path = Carthage/Checkouts/Bluetooth/Xcode/Bluetooth.xcodeproj; sourceTree = ""; };
- 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GATT.xcodeproj; path = Carthage/Checkouts/GATT/Xcode/GATT.xcodeproj; sourceTree = ""; };
- 6EC0D9C620DE076A005FB128 /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = ""; };
- 6ECCCC5A20E0866E0054663C /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; };
- 6ECCCC5C20E091060054663C /* FDStackViewAlignmentLayoutArrangement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDStackViewAlignmentLayoutArrangement.h; sourceTree = ""; };
- 6ECCCC5D20E091060054663C /* FDTransformLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDTransformLayer.m; sourceTree = ""; };
- 6ECCCC5E20E091060054663C /* FDLayoutSpacer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDLayoutSpacer.m; sourceTree = ""; };
- 6ECCCC5F20E091060054663C /* FDStackViewLayoutArrangement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDStackViewLayoutArrangement.h; sourceTree = ""; };
- 6ECCCC6020E091060054663C /* FDStackViewDistributionLayoutArrangement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDStackViewDistributionLayoutArrangement.h; sourceTree = ""; };
- 6ECCCC6120E091060054663C /* FDStackView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDStackView.m; sourceTree = ""; };
- 6ECCCC6220E091060054663C /* FDGapLayoutGuide.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDGapLayoutGuide.h; sourceTree = ""; };
- 6ECCCC6320E091060054663C /* FDStackViewExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDStackViewExtensions.m; sourceTree = ""; };
- 6ECCCC6420E091060054663C /* FDTransformLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDTransformLayer.h; sourceTree = ""; };
- 6ECCCC6520E091060054663C /* FDStackViewAlignmentLayoutArrangement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDStackViewAlignmentLayoutArrangement.m; sourceTree = ""; };
- 6ECCCC6620E091060054663C /* FDStackViewDistributionLayoutArrangement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDStackViewDistributionLayoutArrangement.m; sourceTree = ""; };
- 6ECCCC6720E091060054663C /* FDLayoutSpacer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDLayoutSpacer.h; sourceTree = ""; };
- 6ECCCC6820E091060054663C /* FDStackViewLayoutArrangement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDStackViewLayoutArrangement.m; sourceTree = ""; };
- 6ECCCC6920E091060054663C /* FDGapLayoutGuide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDGapLayoutGuide.m; sourceTree = ""; };
- 6ECCCC6A20E091060054663C /* FDStackView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDStackView.h; sourceTree = ""; };
- 6ECCCC6B20E091060054663C /* FDStackViewExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDStackViewExtensions.h; sourceTree = ""; };
- 6EDE00C520DB0D4F002E951A /* PeripheralCharacteristicDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralCharacteristicDetailViewController.swift; sourceTree = ""; };
- 6EDE00C820DB0EB5002E951A /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; };
- 6EDE00CE20DB12B3002E951A /* BatteryLevelCharacteristic.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BatteryLevelCharacteristic.storyboard; sourceTree = ""; };
- 6EDE00D120DB47FD002E951A /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = ""; };
- 6EDE00D420DB4C84002E951A /* BatteryLevelCharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryLevelCharacteristicViewController.swift; sourceTree = ""; };
- 6EDE00D620DB60D9002E951A /* CharacteristicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacteristicViewController.swift; sourceTree = ""; };
- 6EFB3E2420D3E5A600364CA0 /* DeviceStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStore.swift; sourceTree = ""; };
- 6EFB3E8C20D3ECF200364CA0 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; };
- 6EFB3E9020D3EF6C00364CA0 /* CoreDataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataExtensions.swift; sourceTree = ""; };
- 6EFB3E9220D3EF9A00364CA0 /* ScanDataManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanDataManagedObject.swift; sourceTree = ""; };
- 6EFB3E9420D3EFFB00364CA0 /* PeripheralManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralManagedObject.swift; sourceTree = ""; };
- 6EFB3E9620D3F0D700364CA0 /* CentralManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CentralManagedObject.swift; sourceTree = ""; };
- 6EFB3E9A20D40F2000364CA0 /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; };
- 6EFD557D20D83B8F003B75A1 /* ServiceManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceManagedObject.swift; sourceTree = ""; };
- 6EFD558320D83C76003B75A1 /* CharacteristicManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacteristicManagedObject.swift; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 6E98B6902059B51000B6F016 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 6EC0D9C220DDF876005FB128 /* GATT.framework in Frameworks */,
- 6EC0D9BE20DDF871005FB128 /* Bluetooth.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 35CA5E1E20E5865200F6B589 /* Products */ = {
- isa = PBXGroup;
- children = (
- 35CA5E2520E5865300F6B589 /* Rswift.framework */,
- 35CA5E2720E5865300F6B589 /* RswiftTests-iOS.xctest */,
- 35CA5E2920E5865300F6B589 /* Rswift.framework */,
- 35CA5E2B20E5865300F6B589 /* RswiftTests-tvOS.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 6E8C68DF20DA02AA00232169 /* Extensions */ = {
- isa = PBXGroup;
- children = (
- 6E8C68E820DA02DA00232169 /* AdaptiveNavigation.swift */,
- 6E8C68E420DA02B600232169 /* ErrorAlert.swift */,
- 6E8C68E620DA02DA00232169 /* PresentPopover.swift */,
- 6E8C68E720DA02DA00232169 /* UIAlertAction.swift */,
- );
- path = Extensions;
- sourceTree = "";
- };
- 6E8C68EE20DA030F00232169 /* Protocols */ = {
- isa = PBXGroup;
- children = (
- 6E8C68EC20DA030900232169 /* ActivityIndicatorViewController.swift */,
- 6EDE00D620DB60D9002E951A /* CharacteristicViewController.swift */,
- 353557C620E28CB700543440 /* InstantiableViewController.swift */,
- );
- path = Protocols;
- sourceTree = "";
- };
- 6E98B6942059B51000B6F016 /* BluetoothExplorer */ = {
- isa = PBXGroup;
- children = (
- 6E98B6952059B51000B6F016 /* AppDelegate.swift */,
- 6E98B69E2059B51000B6F016 /* LaunchScreen.storyboard */,
- 6E98B6992059B51000B6F016 /* Main.storyboard */,
- 6EDE00CE20DB12B3002E951A /* BatteryLevelCharacteristic.storyboard */,
- 35298F1920EACCB10093B830 /* BatteryPowerStateCharacteristic.storyboard */,
- 35335A0420E1316F006ABD4D /* ModelNumberCharacteristic.storyboard */,
- 35335A0A20E13FC0006ABD4D /* FirmwareRevisionStringCharacteristic.storyboard */,
- 35335A1520E14353006ABD4D /* SoftwareRevisionStringCharacteristic.storyboard */,
- 35335A1920E14830006ABD4D /* ManufacturerNameStringCharacteristic.storyboard */,
- 35335A1D20E14EFA006ABD4D /* SystemIDCharacteristic.storyboard */,
- 35335A2320E16D4F006ABD4D /* DateTimeCharacteristic.storyboard */,
- 353557D520E2D15000543440 /* PnPIDCharacteristic.storyboard */,
- 35317A2D20E53BD500D31493 /* HardwareRevisionStringCharacteristic.storyboard */,
- 35317A3820E5485400D31493 /* SerialNumberStringCharacteristic.storyboard */,
- 35317A3E20E56B9800D31493 /* AlertCategoryCharacteristic.storyboard */,
- 35298F5A20EB08D10093B830 /* AlertLevelCharacteristic.storyboard */,
- 35386B0120EBC5000016D662 /* AlertNotificationControlPointCharacteristic.storyboard */,
- 35386B1420EBE13E0016D662 /* AgeCharacteristic.storyboard */,
- 35386B4320EBF64A0016D662 /* AerobicHeartRateLowerLimitCharacteristic.storyboard */,
- 35386B4720EBFC1B0016D662 /* AerobicHeartRateUpperLimitCharacteristic.storyboard */,
- 6E98B69C2059B51000B6F016 /* Assets.xcassets */,
- 6E98B6A12059B51000B6F016 /* Info.plist */,
- 35CA5E1720E579B100F6B589 /* Localizable.strings */,
- 6E8C68EF20DA04D400232169 /* Async.swift */,
- 6EDE00C720DB0EA8002E951A /* Extensions */,
- 6EDE00D020DB47C6002E951A /* View */,
- 6EFB3E8F20D3EE1000364CA0 /* Controller */,
- 6EFB3E8E20D3EE0200364CA0 /* Model */,
- );
- path = BluetoothExplorer;
- sourceTree = "";
- };
- 6EC0D99720DDF5E0005FB128 /* Products */ = {
- isa = PBXGroup;
- children = (
- 6EC0D99F20DDF5E0005FB128 /* Bluetooth.framework */,
- 6EC0D9A120DDF5E0005FB128 /* Bluetooth.framework */,
- 6EC0D9A320DDF5E0005FB128 /* Bluetooth.framework */,
- 6EC0D9A520DDF5E0005FB128 /* Bluetooth.framework */,
- 6EC0D9A720DDF5E0005FB128 /* BluetoothTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 6EC0D9A920DDF5E8005FB128 /* Products */ = {
- isa = PBXGroup;
- children = (
- 6EC0D9B120DDF5E8005FB128 /* GATT.framework */,
- 6EC0D9B320DDF5E8005FB128 /* GATT.framework */,
- 6EC0D9B520DDF5E8005FB128 /* GATT.framework */,
- 6EC0D9B720DDF5E8005FB128 /* GATT.framework */,
- );
- name = Products;
- sourceTree = "";
- };
- 6ECCCC5B20E091060054663C /* FDStackView */ = {
- isa = PBXGroup;
- children = (
- 6ECCCC5C20E091060054663C /* FDStackViewAlignmentLayoutArrangement.h */,
- 6ECCCC5D20E091060054663C /* FDTransformLayer.m */,
- 6ECCCC5E20E091060054663C /* FDLayoutSpacer.m */,
- 6ECCCC5F20E091060054663C /* FDStackViewLayoutArrangement.h */,
- 6ECCCC6020E091060054663C /* FDStackViewDistributionLayoutArrangement.h */,
- 6ECCCC6120E091060054663C /* FDStackView.m */,
- 6ECCCC6220E091060054663C /* FDGapLayoutGuide.h */,
- 6ECCCC6320E091060054663C /* FDStackViewExtensions.m */,
- 6ECCCC6420E091060054663C /* FDTransformLayer.h */,
- 6ECCCC6520E091060054663C /* FDStackViewAlignmentLayoutArrangement.m */,
- 6ECCCC6620E091060054663C /* FDStackViewDistributionLayoutArrangement.m */,
- 6ECCCC6720E091060054663C /* FDLayoutSpacer.h */,
- 6ECCCC6820E091060054663C /* FDStackViewLayoutArrangement.m */,
- 6ECCCC6920E091060054663C /* FDGapLayoutGuide.m */,
- 6ECCCC6A20E091060054663C /* FDStackView.h */,
- 6ECCCC6B20E091060054663C /* FDStackViewExtensions.h */,
- );
- name = FDStackView;
- path = Carthage/Checkouts/FDStackView/FDStackView;
- sourceTree = "";
- };
- 6EDE00C720DB0EA8002E951A /* Extensions */ = {
- isa = PBXGroup;
- children = (
- 6EDE00C820DB0EB5002E951A /* Data.swift */,
- 353557CF20E28CD200543440 /* String.swift */,
- 35CA5E0820E5740E00F6B589 /* Integer.swift */,
- 35CA5E1120E5777000F6B589 /* GATTAlertCategory.swift */,
- );
- path = Extensions;
- sourceTree = "";
- };
- 6EDE00D020DB47C6002E951A /* View */ = {
- isa = PBXGroup;
- children = (
- 6EC0D9C620DE076A005FB128 /* Appearance.swift */,
- 6EDE00D120DB47FD002E951A /* CustomButton.swift */,
- 35F5BB5920E3DAC500AC5FF1 /* InputTextView.xib */,
- 35F5BB6220E3DB8A00AC5FF1 /* InputTextView.swift */,
- 35F5BB6420E3DC0000AC5FF1 /* NibDesignable.swift */,
- 35FE560020E41F5B0052C0C0 /* InputTextField.swift */,
- 35FE560D20E435860052C0C0 /* InputTextViewCell.xib */,
- 35FE560F20E436CA0052C0C0 /* InputTextViewCell.swift */,
- );
- path = View;
- sourceTree = "";
- };
- 6EDE00D320DB4C75002E951A /* GATT Characteristic Editors */ = {
- isa = PBXGroup;
- children = (
- 6EDE00D420DB4C84002E951A /* BatteryLevelCharacteristicViewController.swift */,
- 35335A0620E13203006ABD4D /* ModelNumberCharacteristicViewController.swift */,
- 35335A1320E13FF0006ABD4D /* FirmwareRevisionStringCharacteristicViewController.swift */,
- 35335A1720E14392006ABD4D /* SoftwareRevisionStringCharacteristicViewController.swift */,
- 35335A1B20E14846006ABD4D /* ManufacturerNameStringCharacteristicViewController.swift */,
- 35335A2C20E16D74006ABD4D /* DateTimeCharacteristicViewController.swift */,
- 353557DE20E2D17300543440 /* BatteryPowerStateCharacteristicViewController.swift */,
- 35FE560B20E434860052C0C0 /* SystemIDCharacteristicViewController.swift */,
- 35317A3620E53C2C00D31493 /* HardwareRevisionStringCharacteristicViewController.swift */,
- 35317A3A20E5486200D31493 /* SerialNumberStringCharacteristicViewController.swift */,
- 35317A4720E56CCA00D31493 /* AlertCategoryCharacteristicViewController.swift */,
- 35298F4820EAE1D60093B830 /* AlertLevelCharacteristicViewController.swift */,
- 35298F2520EACD700093B830 /* PnPIDCharacteristicViewController.swift */,
- 35386B0D20EBC5920016D662 /* AlertNotificationControlPointCharacteristicViewController.swift */,
- 35386B2020EBE1530016D662 /* AgeCharacteristicViewController.swift */,
- 35386B3720EBF0310016D662 /* AerobicHeartRateLowerLimitCharacteristicViewController.swift */,
- 35386B4520EBFBC50016D662 /* AerobicHeartRateUpperLimitCharacteristicViewController.swift */,
- );
- path = "GATT Characteristic Editors";
- sourceTree = "";
- };
- 6EE84D7A1CAF419D00A40C4D = {
- isa = PBXGroup;
- children = (
- 35CA5E3C20E58F5300F6B589 /* R.generated.swift */,
- 6ECCCC5A20E0866E0054663C /* Cartfile */,
- 6ECCCC5B20E091060054663C /* FDStackView */,
- 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */,
- 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */,
- 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */,
- 6E98B6942059B51000B6F016 /* BluetoothExplorer */,
- 6EE84D851CAF419D00A40C4D /* Products */,
- 6EFB3E6720D3EB0600364CA0 /* Frameworks */,
- );
- sourceTree = "";
- };
- 6EE84D851CAF419D00A40C4D /* Products */ = {
- isa = PBXGroup;
- children = (
- 6E98B6932059B51000B6F016 /* BluetoothExplorer.app */,
- );
- name = Products;
- sourceTree = "";
- };
- 6EFB3E6720D3EB0600364CA0 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 35CA5E1A20E584A100F6B589 /* Rswift.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
- 6EFB3E8E20D3EE0200364CA0 /* Model */ = {
- isa = PBXGroup;
- children = (
- 6EFB3E8B20D3ECF200364CA0 /* Model.xcdatamodeld */,
- 6EFB3E9020D3EF6C00364CA0 /* CoreDataExtensions.swift */,
- 6E353A4520D7FDB200E94B73 /* CoreDataEncodable.swift */,
- 6E353A4B20D7FDC900E94B73 /* CoreDataDecodable.swift */,
- 6EFB3E2420D3E5A600364CA0 /* DeviceStore.swift */,
- 6EFB3E9620D3F0D700364CA0 /* CentralManagedObject.swift */,
- 6EFB3E9420D3EFFB00364CA0 /* PeripheralManagedObject.swift */,
- 6EFB3E9220D3EF9A00364CA0 /* ScanDataManagedObject.swift */,
- 6E13E8CB20D9C7270007111B /* AdvertisementDataManagedObject.swift */,
- 6EFD557D20D83B8F003B75A1 /* ServiceManagedObject.swift */,
- 6EFD558320D83C76003B75A1 /* CharacteristicManagedObject.swift */,
- 6EFB3E9A20D40F2000364CA0 /* PeripheralModel.swift */,
- );
- path = Model;
- sourceTree = "";
- };
- 6EFB3E8F20D3EE1000364CA0 /* Controller */ = {
- isa = PBXGroup;
- children = (
- 6E8C68EE20DA030F00232169 /* Protocols */,
- 6E8C68DF20DA02AA00232169 /* Extensions */,
- 6E13E8E220D9D8290007111B /* TableViewController.swift */,
- 6E98B6972059B51000B6F016 /* PeripheralsViewController.swift */,
- 6E8C68F120DA340C00232169 /* PeripheralServicesViewController.swift */,
- 6E94B2AE20DAC53200404FD9 /* PeripheralCharacteristicsViewController.swift */,
- 6EDE00C520DB0D4F002E951A /* PeripheralCharacteristicDetailViewController.swift */,
- 6EDE00D320DB4C75002E951A /* GATT Characteristic Editors */,
- );
- path = Controller;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 6E98B6922059B51000B6F016 /* BluetoothExplorer */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 6E98B6A42059B51000B6F016 /* Build configuration list for PBXNativeTarget "BluetoothExplorer" */;
- buildPhases = (
- 35CA5E1920E5845700F6B589 /* R swift */,
- 6E98B68F2059B51000B6F016 /* Sources */,
- 6E98B6902059B51000B6F016 /* Frameworks */,
- 6E98B6912059B51000B6F016 /* Resources */,
- 6EFB3E2C20D3E89200364CA0 /* Embed Frameworks */,
- 6ECCCC5920E078F10054663C /* Increment Build */,
- );
- buildRules = (
- );
- dependencies = (
- 6E10553420E9CDEF00D0CD0D /* PBXTargetDependency */,
- 6EC0D9BD20DDF5F6005FB128 /* PBXTargetDependency */,
- 6EC0D9BB20DDF5F6005FB128 /* PBXTargetDependency */,
- 6EC0D9C120DDF871005FB128 /* PBXTargetDependency */,
- 6EC0D9C520DDF877005FB128 /* PBXTargetDependency */,
- );
- name = BluetoothExplorer;
- productName = BluetoothExplorer;
- productReference = 6E98B6932059B51000B6F016 /* BluetoothExplorer.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 6EE84D7B1CAF419D00A40C4D /* Project object */ = {
- isa = PBXProject;
- attributes = {
- LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 0940;
- ORGANIZATIONNAME = PureSwift;
- TargetAttributes = {
- 6E98B6922059B51000B6F016 = {
- CreatedOnToolsVersion = 9.2;
- DevelopmentTeam = 4W79SG34MW;
- ProvisioningStyle = Automatic;
- SystemCapabilities = {
- com.apple.BackgroundModes = {
- enabled = 1;
- };
- };
- };
- };
- };
- buildConfigurationList = 6EE84D7E1CAF419D00A40C4D /* Build configuration list for PBXProject "BluetoothExplorer" */;
- compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 6EE84D7A1CAF419D00A40C4D;
- productRefGroup = 6EE84D851CAF419D00A40C4D /* Products */;
- projectDirPath = "";
- projectReferences = (
- {
- ProductGroup = 6EC0D99720DDF5E0005FB128 /* Products */;
- ProjectRef = 6EC0D99620DDF5E0005FB128 /* Bluetooth.xcodeproj */;
- },
- {
- ProductGroup = 6EC0D9A920DDF5E8005FB128 /* Products */;
- ProjectRef = 6EC0D9A820DDF5E8005FB128 /* GATT.xcodeproj */;
- },
- {
- ProductGroup = 35CA5E1E20E5865200F6B589 /* Products */;
- ProjectRef = 35CA5E1D20E5865200F6B589 /* R.swift.Library.xcodeproj */;
- },
- );
- projectRoot = "";
- targets = (
- 6E98B6922059B51000B6F016 /* BluetoothExplorer */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXReferenceProxy section */
- 35CA5E2520E5865300F6B589 /* Rswift.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = Rswift.framework;
- remoteRef = 35CA5E2420E5865300F6B589 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 35CA5E2720E5865300F6B589 /* RswiftTests-iOS.xctest */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.cfbundle;
- path = "RswiftTests-iOS.xctest";
- remoteRef = 35CA5E2620E5865300F6B589 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 35CA5E2920E5865300F6B589 /* Rswift.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = Rswift.framework;
- remoteRef = 35CA5E2820E5865300F6B589 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 35CA5E2B20E5865300F6B589 /* RswiftTests-tvOS.xctest */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.cfbundle;
- path = "RswiftTests-tvOS.xctest";
- remoteRef = 35CA5E2A20E5865300F6B589 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D99F20DDF5E0005FB128 /* Bluetooth.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = Bluetooth.framework;
- remoteRef = 6EC0D99E20DDF5E0005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9A120DDF5E0005FB128 /* Bluetooth.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = Bluetooth.framework;
- remoteRef = 6EC0D9A020DDF5E0005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9A320DDF5E0005FB128 /* Bluetooth.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = Bluetooth.framework;
- remoteRef = 6EC0D9A220DDF5E0005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9A520DDF5E0005FB128 /* Bluetooth.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = Bluetooth.framework;
- remoteRef = 6EC0D9A420DDF5E0005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9A720DDF5E0005FB128 /* BluetoothTests.xctest */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.cfbundle;
- path = BluetoothTests.xctest;
- remoteRef = 6EC0D9A620DDF5E0005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9B120DDF5E8005FB128 /* GATT.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = GATT.framework;
- remoteRef = 6EC0D9B020DDF5E8005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9B320DDF5E8005FB128 /* GATT.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = GATT.framework;
- remoteRef = 6EC0D9B220DDF5E8005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9B520DDF5E8005FB128 /* GATT.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = GATT.framework;
- remoteRef = 6EC0D9B420DDF5E8005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 6EC0D9B720DDF5E8005FB128 /* GATT.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = GATT.framework;
- remoteRef = 6EC0D9B620DDF5E8005FB128 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
-/* End PBXReferenceProxy section */
-
-/* Begin PBXResourcesBuildPhase section */
- 6E98B6912059B51000B6F016 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 35298F1A20EACCB10093B830 /* BatteryPowerStateCharacteristic.storyboard in Resources */,
- 35CA5E1520E579B100F6B589 /* Localizable.strings in Resources */,
- 6EDE00CF20DB12B3002E951A /* BatteryLevelCharacteristic.storyboard in Resources */,
- 6E98B6A02059B51000B6F016 /* LaunchScreen.storyboard in Resources */,
- 35335A0520E1316F006ABD4D /* ModelNumberCharacteristic.storyboard in Resources */,
- 35386B4820EBFC1B0016D662 /* AerobicHeartRateUpperLimitCharacteristic.storyboard in Resources */,
- 35386B0220EBC5000016D662 /* AlertNotificationControlPointCharacteristic.storyboard in Resources */,
- 35317A3920E5485400D31493 /* SerialNumberStringCharacteristic.storyboard in Resources */,
- 35298F5B20EB08D10093B830 /* AlertLevelCharacteristic.storyboard in Resources */,
- 35317A2E20E53BD500D31493 /* HardwareRevisionStringCharacteristic.storyboard in Resources */,
- 6E98B69D2059B51000B6F016 /* Assets.xcassets in Resources */,
- 6E98B69B2059B51000B6F016 /* Main.storyboard in Resources */,
- 35335A1E20E14EFA006ABD4D /* SystemIDCharacteristic.storyboard in Resources */,
- 353557D620E2D15000543440 /* PnPIDCharacteristic.storyboard in Resources */,
- 35FE560E20E435860052C0C0 /* InputTextViewCell.xib in Resources */,
- 35335A1A20E14830006ABD4D /* ManufacturerNameStringCharacteristic.storyboard in Resources */,
- 35335A1220E13FC0006ABD4D /* FirmwareRevisionStringCharacteristic.storyboard in Resources */,
- 35386B4420EBF64A0016D662 /* AerobicHeartRateLowerLimitCharacteristic.storyboard in Resources */,
- 35317A3F20E56B9800D31493 /* AlertCategoryCharacteristic.storyboard in Resources */,
- 35335A1620E14353006ABD4D /* SoftwareRevisionStringCharacteristic.storyboard in Resources */,
- 35F5BB5A20E3DAC500AC5FF1 /* InputTextView.xib in Resources */,
- 35386B1520EBE13E0016D662 /* AgeCharacteristic.storyboard in Resources */,
- 35335A2B20E16D4F006ABD4D /* DateTimeCharacteristic.storyboard in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- 35CA5E1920E5845700F6B589 /* R swift */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "R swift";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"$SRCROOT/rswift\" generate \"$SRCROOT\"";
- };
- 6ECCCC5920E078F10054663C /* Increment Build */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "Increment Build";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "#!/bin/bash\n\n# update_build_number.sh\n# Usage: `update_build_number.sh [branch]`\n# Run this script after the 'Copy Bundle Resources' build phase\n# Ref: http://tgoode.com/2014/06/05/sensible-way-increment-bundle-version-cfbundleversion-xcode/\n\nif [ $TRAVIS == \"true\" ]\nthen echo \"Updating build number to TRAVISCI.\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion TRAVISCI\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nelse branch=${1:-'master'}\nbuildNumber=$(expr $(git rev-list $branch --count) - $(git rev-list HEAD..$branch --count))\necho \"Updating build number to $buildNumber using branch '$branch'.\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nfi";
- };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 6E98B68F2059B51000B6F016 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 6E13E8CC20D9C7270007111B /* AdvertisementDataManagedObject.swift in Sources */,
- 6E8C68E920DA02DA00232169 /* PresentPopover.swift in Sources */,
- 6EFB3E9520D3EFFB00364CA0 /* PeripheralManagedObject.swift in Sources */,
- 35335A1C20E14846006ABD4D /* ManufacturerNameStringCharacteristicViewController.swift in Sources */,
- 35FE561020E436CA0052C0C0 /* InputTextViewCell.swift in Sources */,
- 35CA5E1220E5777000F6B589 /* GATTAlertCategory.swift in Sources */,
- 35F5BB6320E3DB8A00AC5FF1 /* InputTextView.swift in Sources */,
- 6ECCCC6E20E091070054663C /* FDStackView.m in Sources */,
- 6E8C68F220DA340C00232169 /* PeripheralServicesViewController.swift in Sources */,
- 6E98B6982059B51000B6F016 /* PeripheralsViewController.swift in Sources */,
- 6ECCCC6D20E091070054663C /* FDLayoutSpacer.m in Sources */,
- 6EFD557E20D83B8F003B75A1 /* ServiceManagedObject.swift in Sources */,
- 6EDE00C920DB0EB5002E951A /* Data.swift in Sources */,
- 6EFB3E2520D3E5A600364CA0 /* DeviceStore.swift in Sources */,
- 353557C720E28CB700543440 /* InstantiableViewController.swift in Sources */,
- 6ECCCC7320E091070054663C /* FDGapLayoutGuide.m in Sources */,
- 35335A1420E13FF0006ABD4D /* FirmwareRevisionStringCharacteristicViewController.swift in Sources */,
- 35386B4620EBFBC50016D662 /* AerobicHeartRateUpperLimitCharacteristicViewController.swift in Sources */,
- 6E8C68EA20DA02DA00232169 /* UIAlertAction.swift in Sources */,
- 35335A0720E13203006ABD4D /* ModelNumberCharacteristicViewController.swift in Sources */,
- 35386B0E20EBC5920016D662 /* AlertNotificationControlPointCharacteristicViewController.swift in Sources */,
- 35298F2620EACD700093B830 /* PnPIDCharacteristicViewController.swift in Sources */,
- 6EC0D9C720DE076A005FB128 /* Appearance.swift in Sources */,
- 6EFB3E9720D3F0D700364CA0 /* CentralManagedObject.swift in Sources */,
- 6EFD558420D83C76003B75A1 /* CharacteristicManagedObject.swift in Sources */,
- 6EDE00D520DB53B4002E951A /* BatteryLevelCharacteristicViewController.swift in Sources */,
- 6E94B2AF20DAC53200404FD9 /* PeripheralCharacteristicsViewController.swift in Sources */,
- 6E353A4C20D7FDC900E94B73 /* CoreDataDecodable.swift in Sources */,
- 6E8C68F020DA04D500232169 /* Async.swift in Sources */,
- 35317A4820E56CCA00D31493 /* AlertCategoryCharacteristicViewController.swift in Sources */,
- 6E353A4620D7FDB200E94B73 /* CoreDataEncodable.swift in Sources */,
- 6EDE00D220DB47FD002E951A /* CustomButton.swift in Sources */,
- 353557D020E28CD200543440 /* String.swift in Sources */,
- 6EFB3E9120D3EF6C00364CA0 /* CoreDataExtensions.swift in Sources */,
- 35CA5E0920E5740E00F6B589 /* Integer.swift in Sources */,
- 6EFB3E9B20D40F2000364CA0 /* PeripheralModel.swift in Sources */,
- 6E8C68ED20DA030A00232169 /* ActivityIndicatorViewController.swift in Sources */,
- 6ECCCC7120E091070054663C /* FDStackViewDistributionLayoutArrangement.m in Sources */,
- 6ECCCC6F20E091070054663C /* FDStackViewExtensions.m in Sources */,
- 35298F4920EAE1D60093B830 /* AlertLevelCharacteristicViewController.swift in Sources */,
- 6EFB3E9320D3EF9A00364CA0 /* ScanDataManagedObject.swift in Sources */,
- 6ECCCC6C20E091070054663C /* FDTransformLayer.m in Sources */,
- 35386B3820EBF0310016D662 /* AerobicHeartRateLowerLimitCharacteristicViewController.swift in Sources */,
- 6E98B6962059B51000B6F016 /* AppDelegate.swift in Sources */,
- 353557DF20E2D17300543440 /* BatteryPowerStateCharacteristicViewController.swift in Sources */,
- 35F5BB6520E3DC0000AC5FF1 /* NibDesignable.swift in Sources */,
- 35FE560C20E434860052C0C0 /* SystemIDCharacteristicViewController.swift in Sources */,
- 6E8C68E520DA02B600232169 /* ErrorAlert.swift in Sources */,
- 35CA5E3D20E58F5400F6B589 /* R.generated.swift in Sources */,
- 35FE560120E41F5B0052C0C0 /* InputTextField.swift in Sources */,
- 6ECCCC7020E091070054663C /* FDStackViewAlignmentLayoutArrangement.m in Sources */,
- 6ECCCC7220E091070054663C /* FDStackViewLayoutArrangement.m in Sources */,
- 35317A3B20E5486200D31493 /* SerialNumberStringCharacteristicViewController.swift in Sources */,
- 6E8C68EB20DA02DA00232169 /* AdaptiveNavigation.swift in Sources */,
- 6E13E8E320D9D8290007111B /* TableViewController.swift in Sources */,
- 35335A1820E14392006ABD4D /* SoftwareRevisionStringCharacteristicViewController.swift in Sources */,
- 35335A2D20E16D74006ABD4D /* DateTimeCharacteristicViewController.swift in Sources */,
- 35317A3720E53C2C00D31493 /* HardwareRevisionStringCharacteristicViewController.swift in Sources */,
- 6EDE00D720DB60D9002E951A /* CharacteristicViewController.swift in Sources */,
- 6EDE00C620DB0D4F002E951A /* PeripheralCharacteristicDetailViewController.swift in Sources */,
- 35386B2120EBE1530016D662 /* AgeCharacteristicViewController.swift in Sources */,
- 6EFB3E8D20D3ECF200364CA0 /* Model.xcdatamodeld in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 6E10553420E9CDEF00D0CD0D /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = "Rswift-iOS";
- targetProxy = 6E10553320E9CDEF00D0CD0D /* PBXContainerItemProxy */;
- };
- 6EC0D9BB20DDF5F6005FB128 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = "GATT-iOS";
- targetProxy = 6EC0D9BA20DDF5F6005FB128 /* PBXContainerItemProxy */;
- };
- 6EC0D9BD20DDF5F6005FB128 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = "Bluetooth-iOS";
- targetProxy = 6EC0D9BC20DDF5F6005FB128 /* PBXContainerItemProxy */;
- };
- 6EC0D9C120DDF871005FB128 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = "Bluetooth-iOS";
- targetProxy = 6EC0D9C020DDF871005FB128 /* PBXContainerItemProxy */;
- };
- 6EC0D9C520DDF877005FB128 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = "GATT-iOS";
- targetProxy = 6EC0D9C420DDF877005FB128 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 35CA5E1720E579B100F6B589 /* Localizable.strings */ = {
- isa = PBXVariantGroup;
- children = (
- 35CA5E1620E579B100F6B589 /* en */,
- );
- name = Localizable.strings;
- sourceTree = "";
- };
- 6E98B6992059B51000B6F016 /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 6E98B69A2059B51000B6F016 /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "";
- };
- 6E98B69E2059B51000B6F016 /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 6E98B69F2059B51000B6F016 /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 6E98B6A22059B51000B6F016 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = 4W79SG34MW;
- FRAMEWORK_SEARCH_PATHS = (
- $SRCROOT/../Carthage/Build/iOS,
- "$(PROJECT_DIR)/Carthage/Build/iOS",
- );
- GCC_C_LANGUAGE_STANDARD = gnu11;
- INFOPLIST_FILE = BluetoothExplorer/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- PRODUCT_BUNDLE_IDENTIFIER = com.pureswift.BluetoothExplorer;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SDKROOT = iphoneos;
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_VERSION = 4.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Debug;
- };
- 6E98B6A32059B51000B6F016 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = 4W79SG34MW;
- FRAMEWORK_SEARCH_PATHS = (
- $SRCROOT/../Carthage/Build/iOS,
- "$(PROJECT_DIR)/Carthage/Build/iOS",
- );
- GCC_C_LANGUAGE_STANDARD = gnu11;
- INFOPLIST_FILE = BluetoothExplorer/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- PRODUCT_BUNDLE_IDENTIFIER = com.pureswift.BluetoothExplorer;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SDKROOT = iphoneos;
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 4.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 6EE84D961CAF419D00A40C4D /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGNING_REQUIRED = NO;
- CODE_SIGN_IDENTITY = "-";
- COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- INFOPLIST_FILE = GATT/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 8.2;
- MACOSX_DEPLOYMENT_TARGET = 10.10;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- OTHER_CFLAGS = "";
- OTHER_SWIFT_FLAGS = "-DXcode";
- PRODUCT_BUNDLE_IDENTIFIER = org.pureswift.GATT;
- PRODUCT_NAME = "$(PROJECT_NAME)";
- SDKROOT = macosx;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 3.0;
- TVOS_DEPLOYMENT_TARGET = 9.0;
- VERSIONING_SYSTEM = "apple-generic";
- VERSION_INFO_PREFIX = "";
- WATCHOS_DEPLOYMENT_TARGET = 2.0;
- };
- name = Debug;
- };
- 6EE84D971CAF419D00A40C4D /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGNING_REQUIRED = NO;
- CODE_SIGN_IDENTITY = "-";
- COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- INFOPLIST_FILE = GATT/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 8.2;
- MACOSX_DEPLOYMENT_TARGET = 10.10;
- MTL_ENABLE_DEBUG_INFO = NO;
- OTHER_CFLAGS = "";
- OTHER_SWIFT_FLAGS = "-DXcode";
- PRODUCT_BUNDLE_IDENTIFIER = org.pureswift.GATT;
- PRODUCT_NAME = "$(PROJECT_NAME)";
- SDKROOT = macosx;
- SWIFT_VERSION = 3.0;
- TVOS_DEPLOYMENT_TARGET = 9.0;
- VERSIONING_SYSTEM = "apple-generic";
- VERSION_INFO_PREFIX = "";
- WATCHOS_DEPLOYMENT_TARGET = 2.0;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 6E98B6A42059B51000B6F016 /* Build configuration list for PBXNativeTarget "BluetoothExplorer" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 6E98B6A22059B51000B6F016 /* Debug */,
- 6E98B6A32059B51000B6F016 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 6EE84D7E1CAF419D00A40C4D /* Build configuration list for PBXProject "BluetoothExplorer" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 6EE84D961CAF419D00A40C4D /* Debug */,
- 6EE84D971CAF419D00A40C4D /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
-
-/* Begin XCVersionGroup section */
- 6EFB3E8B20D3ECF200364CA0 /* Model.xcdatamodeld */ = {
- isa = XCVersionGroup;
- children = (
- 6EFB3E8C20D3ECF200364CA0 /* Model.xcdatamodel */,
- );
- currentVersion = 6EFB3E8C20D3ECF200364CA0 /* Model.xcdatamodel */;
- path = Model.xcdatamodeld;
- sourceTree = "";
- versionGroupType = wrapper.xcdatamodel;
- };
-/* End XCVersionGroup section */
- };
- rootObject = 6EE84D7B1CAF419D00A40C4D /* Project object */;
-}
diff --git a/BluetoothExplorer/AerobicHeartRateLowerLimitCharacteristic.storyboard b/BluetoothExplorer/AerobicHeartRateLowerLimitCharacteristic.storyboard
deleted file mode 100644
index 5e4024a..0000000
--- a/BluetoothExplorer/AerobicHeartRateLowerLimitCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/AerobicHeartRateUpperLimitCharacteristic.storyboard b/BluetoothExplorer/AerobicHeartRateUpperLimitCharacteristic.storyboard
deleted file mode 100644
index 85f8c75..0000000
--- a/BluetoothExplorer/AerobicHeartRateUpperLimitCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/AgeCharacteristic.storyboard b/BluetoothExplorer/AgeCharacteristic.storyboard
deleted file mode 100644
index 678c4fd..0000000
--- a/BluetoothExplorer/AgeCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/AlertCategoryCharacteristic.storyboard b/BluetoothExplorer/AlertCategoryCharacteristic.storyboard
deleted file mode 100644
index dc137fc..0000000
--- a/BluetoothExplorer/AlertCategoryCharacteristic.storyboard
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/AlertLevelCharacteristic.storyboard b/BluetoothExplorer/AlertLevelCharacteristic.storyboard
deleted file mode 100644
index 2fd27da..0000000
--- a/BluetoothExplorer/AlertLevelCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/AlertNotificationControlPointCharacteristic.storyboard b/BluetoothExplorer/AlertNotificationControlPointCharacteristic.storyboard
deleted file mode 100644
index 458e129..0000000
--- a/BluetoothExplorer/AlertNotificationControlPointCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/AppDelegate.swift b/BluetoothExplorer/AppDelegate.swift
deleted file mode 100644
index e3c61b9..0000000
--- a/BluetoothExplorer/AppDelegate.swift
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-// AppDelegate.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 3/14/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-
-@UIApplicationMain
-final class AppDelegate: UIResponder, UIApplicationDelegate {
-
- static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate }
-
- var window: UIWindow?
-
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
-
- ConfigureAppearance()
-
- DeviceStore.shared.centralManager.log = { print("CentralManager:", $0) }
-
- return true
- }
-
- func applicationWillResignActive(_ application: UIApplication) {
- // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
- // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
- }
-
- func applicationDidEnterBackground(_ application: UIApplication) {
- // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
- // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
- }
-
- func applicationWillEnterForeground(_ application: UIApplication) {
- // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
- }
-
- func applicationDidBecomeActive(_ application: UIApplication) {
- // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
- }
-
- func applicationWillTerminate(_ application: UIApplication) {
- // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
- }
-
-
-}
-
diff --git a/BluetoothExplorer/Assets.xcassets/Colors/NavigationBarTintColor.colorset/Contents.json b/BluetoothExplorer/Assets.xcassets/Colors/NavigationBarTintColor.colorset/Contents.json
deleted file mode 100644
index 5efe6ef..0000000
--- a/BluetoothExplorer/Assets.xcassets/Colors/NavigationBarTintColor.colorset/Contents.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- },
- "colors" : [
- {
- "idiom" : "universal",
- "color" : {
- "color-space" : "srgb",
- "components" : {
- "red" : "0.386",
- "alpha" : "1.000",
- "blue" : "1.000",
- "green" : "0.707"
- }
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/Contents.json b/BluetoothExplorer/Assets.xcassets/Tab Bar/Contents.json
deleted file mode 100644
index da4a164..0000000
--- a/BluetoothExplorer/Assets.xcassets/Tab Bar/Contents.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/BluetoothExplorer/Async.swift b/BluetoothExplorer/Async.swift
deleted file mode 100644
index 869be88..0000000
--- a/BluetoothExplorer/Async.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-//
-// Async.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/19/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-
-func mainQueue(_ block: @escaping () -> ()) {
-
- OperationQueue.main.addOperation(block)
-}
-
-/// Perform a task on the internal queue.
-func async(_ block: @escaping () -> ()) {
-
- queue.async { block() }
-}
-
-let queue = DispatchQueue(label: "App Queue")
diff --git a/BluetoothExplorer/Base.lproj/LaunchScreen.storyboard b/BluetoothExplorer/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index 1d4168e..0000000
--- a/BluetoothExplorer/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/Base.lproj/Main.storyboard b/BluetoothExplorer/Base.lproj/Main.storyboard
deleted file mode 100644
index 403c39a..0000000
--- a/BluetoothExplorer/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,465 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/BatteryLevelCharacteristic.storyboard b/BluetoothExplorer/BatteryLevelCharacteristic.storyboard
deleted file mode 100644
index f21338a..0000000
--- a/BluetoothExplorer/BatteryLevelCharacteristic.storyboard
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/BatteryPowerStateCharacteristic.storyboard b/BluetoothExplorer/BatteryPowerStateCharacteristic.storyboard
deleted file mode 100644
index 32963a0..0000000
--- a/BluetoothExplorer/BatteryPowerStateCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/Controller/Extensions/AdaptiveNavigation.swift b/BluetoothExplorer/Controller/Extensions/AdaptiveNavigation.swift
deleted file mode 100644
index fdcabab..0000000
--- a/BluetoothExplorer/Controller/Extensions/AdaptiveNavigation.swift
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// AdaptiveNavigation.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/19/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-
-internal extension UIViewController {
-
- func showAdaptiveDetail(_ viewController: UIViewController, sender: Any? = nil) {
-
- // iPhone
- if splitViewController?.viewControllers.count == 1 {
-
- self.show(viewController, sender: sender)
- }
- // iPad
- else {
-
- let navigationController = UINavigationController(rootViewController: viewController)
-
- self.showDetailViewController(navigationController, sender: sender)
- }
- }
-}
diff --git a/BluetoothExplorer/Controller/Extensions/PresentPopover.swift b/BluetoothExplorer/Controller/Extensions/PresentPopover.swift
deleted file mode 100644
index d818c07..0000000
--- a/BluetoothExplorer/Controller/Extensions/PresentPopover.swift
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// PresentPopover.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/19/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-
-extension UIViewController {
-
- func present(_ viewController: UIViewController,
- animated: Bool = true,
- completion: (() -> Void)? = nil,
- sender: PopoverPresentingView) {
-
- viewController.modalPresentationStyle = .popover
-
- switch sender {
-
- case let .view(view):
-
- viewController.popoverPresentationController?.sourceRect = view.bounds
- viewController.popoverPresentationController?.sourceView = view
-
- case let .barButtonItem(tabBarItem):
-
- viewController.popoverPresentationController?.barButtonItem = tabBarItem
- }
-
- self.present(viewController, animated: animated, completion: completion)
- }
-}
-
-// MARK: - Supporting Types
-
-enum PopoverPresentingView {
-
- case view(UIView)
- case barButtonItem(UIBarButtonItem)
-}
diff --git a/BluetoothExplorer/Controller/Extensions/UIAlertAction.swift b/BluetoothExplorer/Controller/Extensions/UIAlertAction.swift
deleted file mode 100644
index faa4f8a..0000000
--- a/BluetoothExplorer/Controller/Extensions/UIAlertAction.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// UIAlertAction.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/19/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-
-extension UIAlertController {
-
- enum DefaultAction {
-
- case cancel
- }
-
- func addAction(_ action: DefaultAction) {
-
- let alertAction: UIAlertAction
-
- switch action {
-
- case .cancel:
-
- alertAction = UIAlertAction(title: "cancel", style: .cancel) { [unowned self] _ in
-
- self.dismiss(animated: true, completion: nil)
- }
- }
-
- addAction(alertAction)
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/AerobicHeartRateLowerLimitCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/AerobicHeartRateLowerLimitCharacteristicViewController.swift
deleted file mode 100644
index d941212..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/AerobicHeartRateLowerLimitCharacteristicViewController.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-// AerobicHeartRateLowerLimitCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 7/3/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class AerobicHeartRateLowerLimitCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - Properties
-
- private let cellIdentifier = R.nib.inputTextViewCell.name
-
- private var fields = [Field]()
-
- var value = GATTAerobicHeartRateLowerLimit(beats: 98)
-
- var valueDidChange: ((GATTAerobicHeartRateLowerLimit) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.lowerLimit("\(value.beats.rawValue)")]
- tableView.register(R.nib.inputTextViewCell(), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.keyboardType = field.keyboardType
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- cell.inputTextView.validator = { value in
-
- guard value.trim() != ""
- else { return .none }
-
- switch field {
- case .lowerLimit:
-
- guard let _ = UInt8(value)
- else { return .error("Maximum value is 0xFF") }
- }
-
- return .none
- }
- }
-}
-
-extension AerobicHeartRateLowerLimitCharacteristicViewController {
-
- enum Field {
-
- case lowerLimit(String)
-
- var title: String {
-
- switch self {
- case .lowerLimit: return "Lower Limit"
- }
- }
-
- var bluetoothValue: String {
-
- switch self {
- case .lowerLimit(let value): return value
- }
- }
-
- var keyboardType: UIKeyboardType { return .numberPad }
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/AerobicHeartRateUpperLimitCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/AerobicHeartRateUpperLimitCharacteristicViewController.swift
deleted file mode 100644
index ff87ad8..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/AerobicHeartRateUpperLimitCharacteristicViewController.swift
+++ /dev/null
@@ -1,109 +0,0 @@
-//
-// AerobicHeartRateUpperLimitCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 7/3/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class AerobicHeartRateUpperLimitCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - Properties
-
- private let cellIdentifier = R.nib.inputTextViewCell.name
-
- private var fields = [Field]()
-
- var value = GATTAerobicHeartRateUpperLimit(beats: 98)
-
- var valueDidChange: ((GATTAerobicHeartRateUpperLimit) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.upperLimit("\(value.beats.rawValue)")]
- tableView.register(R.nib.inputTextViewCell(), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.keyboardType = field.keyboardType
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- cell.inputTextView.validator = { value in
-
- guard value.trim() != ""
- else { return .none }
-
- switch field {
- case .upperLimit:
-
- guard let _ = UInt8(value)
- else { return .error("Maximum value is 0xFF") }
- }
-
- return .none
- }
- }
-}
-
-extension AerobicHeartRateUpperLimitCharacteristicViewController {
-
- enum Field {
-
- case upperLimit(String)
-
- var title: String {
-
- switch self {
- case .upperLimit: return "Upper Limit"
- }
- }
-
- var bluetoothValue: String {
-
- switch self {
- case .upperLimit(let value): return value
- }
- }
-
- var keyboardType: UIKeyboardType { return .numberPad }
- }
-}
-
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/AgeCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/AgeCharacteristicViewController.swift
deleted file mode 100644
index 53b680f..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/AgeCharacteristicViewController.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-// AgeCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 7/3/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class AgeCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - Properties
-
- private let cellIdentifier = R.nib.inputTextViewCell.name
-
- private var fields = [Field]()
-
- var value = GATTAge(year: GATTAge.Year(rawValue: 0))
-
- var valueDidChange: ((GATTAge) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.age("\(value.year.rawValue)")]
- tableView.register(R.nib.inputTextViewCell(), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.keyboardType = field.keyboardType
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- cell.inputTextView.validator = { value in
-
- guard value.trim() != ""
- else { return .none }
-
- switch field {
- case .age:
-
- guard let _ = UInt8(value)
- else { return .error("Maximum value is 0xFF") }
- }
-
- return .none
- }
- }
-}
-
-extension AgeCharacteristicViewController {
-
- enum Field {
-
- case age(String)
-
- var title: String {
-
- switch self {
- case .age: return "Age"
- }
- }
-
- var bluetoothValue: String {
-
- switch self {
- case .age(let value): return value
- }
- }
-
- var keyboardType: UIKeyboardType { return .numberPad }
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertCategoryCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertCategoryCharacteristicViewController.swift
deleted file mode 100644
index 43a95b4..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertCategoryCharacteristicViewController.swift
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-// AlertCategoryCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/28/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class AlertCategoryCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - Properties
-
- @IBOutlet weak var alertCategoryTextField: InputTextField!
-
- var value: GATTAlertCategory = .simpleAlert
-
- var valueDidChange: ((GATTAlertCategory) -> ())?
-
- // MARK: - Life cycle
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- let values: [GATTAlertCategory] = [.simpleAlert, .email, .news, .call, .missedCall, .sms, .voiceMail,
- .schedule, .highPrioritizedAlert, .instantMessage]
-
- let haxedecimals: [String] = values.sorted(by: { $1.rawValue > $0.rawValue }).map { "\($0.name) - 0x\($0.rawValue.toHexadecimal())" }
-
- alertCategoryTextField.posibleValues = haxedecimals
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateText()
- }
-
- private func updateText() {
-
- alertCategoryTextField.isEnabled = valueDidChange != nil
- alertCategoryTextField.text = "0x\(value.rawValue.toHexadecimal())"
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertLevelCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertLevelCharacteristicViewController.swift
deleted file mode 100644
index 3ef2600..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertLevelCharacteristicViewController.swift
+++ /dev/null
@@ -1,105 +0,0 @@
-//
-// AlertLevelCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 7/2/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class AlertLevelCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - Properties
-
- private let cellIdentifier = R.nib.inputTextViewCell.name
-
- private var fields = [Field]()
-
- var value: GATTAlertLevel = .none
-
- var valueDidChange: ((GATTAlertLevel) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.level("\(value.rawValue)")]
- tableView.register(R.nib.inputTextViewCell(), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.posibleInputValues = field.posibleValues
- cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.keyboardType = field.keyboardType
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- }
-}
-
-extension AlertLevelCharacteristicViewController {
-
- enum Field {
-
- case level(String)
-
- var title: String {
-
- switch self {
- case .level: return "Level"
- }
- }
-
- var bluetoothValue: String {
-
- switch self {
- case .level(let value): return value
- }
- }
-
- var posibleValues: [String] {
-
- switch self {
- case .level:
- return [GATTAlertLevel.none.rawValue.description,
- GATTAlertLevel.mild.rawValue.description,
- GATTAlertLevel.high.rawValue.description]
- }
- }
-
- var keyboardType: UIKeyboardType { return .default }
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertNotificationControlPointCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertNotificationControlPointCharacteristicViewController.swift
deleted file mode 100644
index dda1398..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/AlertNotificationControlPointCharacteristicViewController.swift
+++ /dev/null
@@ -1,126 +0,0 @@
-//
-// AlertNotificationControlPointCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 7/3/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import Bluetooth
-
-final class AlertNotificationControlPointCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- typealias Command = GATTAlertNotificationControlPoint.Command
-
- // MARK: - Properties
-
- private let cellIdentifier = R.nib.inputTextViewCell.name
-
- private var fields = [Field]()
-
- var value = GATTAlertNotificationControlPoint(command: .enableNewIncomingAlertNotification, category: .simpleAlert)
-
- var valueDidChange: ((GATTAlertNotificationControlPoint) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.command("\(value.command.rawValue)"),
- .alertCategory("\(value.category.rawValue)")]
-
- tableView.register(R.nib.inputTextViewCell(), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.posibleInputValues = field.posibleValues
- cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- }
-}
-
-extension AlertNotificationControlPointCharacteristicViewController {
-
- enum Field {
-
- case command(String)
- case alertCategory(String)
-
- var title: String {
-
- switch self {
- case .command:
- return "Command"
-
- case .alertCategory:
- return "Alert Category"
- }
- }
-
- var bluetoothValue: String {
- switch self {
- case .command(let value):
- return value
-
- case .alertCategory(let value):
- return value
- }
- }
-
- var posibleValues: [String] {
- switch self {
- case .command:
- return [Command.enableNewIncomingAlertNotification.rawValue.description,
- Command.enableUnreadCategoryStatusNotification.rawValue.description,
- Command.disableNewIncomingAlertNotification.rawValue.description,
- Command.disableUnreadCategoryStatusNotification.rawValue.description,
- Command.notifyNewIncomingAlertImmediately.rawValue.description,
- Command.notifyUnreadCategoryStatusImmediately.rawValue.description]
-
- case .alertCategory:
- return [GATTAlertCategory.simpleAlert.rawValue.description,
- GATTAlertCategory.email.rawValue.description,
- GATTAlertCategory.news.rawValue.description,
- GATTAlertCategory.call.rawValue.description,
- GATTAlertCategory.missedCall.rawValue.description,
- GATTAlertCategory.sms.rawValue.description,
- GATTAlertCategory.voiceMail.rawValue.description,
- GATTAlertCategory.schedule.rawValue.description,
- GATTAlertCategory.highPrioritizedAlert.rawValue.description,
- GATTAlertCategory.instantMessage.rawValue.description]
- }
- }
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/BatteryLevelCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/BatteryLevelCharacteristicViewController.swift
deleted file mode 100644
index 1e3c857..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/BatteryLevelCharacteristicViewController.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-// BatteryLevelCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/20/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class BatteryLevelCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var textLabel: UILabel!
-
- @IBOutlet private(set) var slider: UISlider!
-
- // MARK: - Properties
-
- var value = GATTBatteryLevel(level: .min)
-
- var valueDidChange: ((GATTBatteryLevel) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func sliderValueChanged(_ sender: UISlider) {
-
- guard let level = GATTBatteryPercentage(rawValue: UInt8(sender.value))
- else { assertionFailure("Invalid value \(sender.value)"); return }
-
- value = GATTBatteryLevel(level: level)
- updatePercentageText()
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updatePercentageText()
- updateSlider()
- }
-
- func updateSlider() {
-
- slider.isEnabled = valueDidChange != nil
- slider.value = Float(value.level.rawValue)
- }
-
- func updatePercentageText() {
-
- textLabel.text = value.description
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/BatteryPowerStateCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/BatteryPowerStateCharacteristicViewController.swift
deleted file mode 100644
index 4147da0..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/BatteryPowerStateCharacteristicViewController.swift
+++ /dev/null
@@ -1,138 +0,0 @@
-//
-// PnPIDCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/26/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class BatteryPowerStateCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- typealias BatteryPresentState = GATTBatteryPowerState.BatteryPresentState
- typealias BatteryDischargeState = GATTBatteryPowerState.BatteryDischargeState
- typealias BatteryChargeState = GATTBatteryPowerState.BatteryChargeState
- typealias BatteryLevelState = GATTBatteryPowerState.BatteryLevelState
-
- // MARK: - Properties
-
- private let cellIdentifier = "InputTextViewCell"
-
- private var fields = [Field]()
-
- var value = GATTBatteryPowerState(presentState: .unknown, dischargeState: .unknown, chargeState: .unknown, levelState: .unknown)
-
- var valueDidChange: ((GATTBatteryPowerState) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.present("\(value.presentState.rawValue)"),
- .discharge("\(value.dischargeState.rawValue)"),
- .charge("\(value.chargeState.rawValue)"),
- .level("\(value.levelState.rawValue)")]
-
- tableView.register(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.posibleInputValues = field.posibleValues
- cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.keyboardType = field.keyboardType
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- }
-}
-
-extension BatteryPowerStateCharacteristicViewController {
-
- enum Field {
-
- case present(String)
- case discharge(String)
- case charge(String)
- case level(String)
-
- var title: String {
-
- switch self {
- case .present: return "Present State"
- case .discharge: return "Discharge State"
- case .charge: return "Charge State"
- case .level: return "Level State"
- }
- }
-
- var bluetoothValue: String {
-
- switch self {
- case .present(let value): return value
- case .discharge(let value): return value
- case .charge(let value): return value
- case .level(let value): return value
- }
- }
-
- var posibleValues: [String] {
- switch self {
- case .present:
- return [BatteryPresentState.unknown.rawValue.description,
- BatteryPresentState.notSupported.rawValue.description,
- BatteryPresentState.notPresent.rawValue.description,
- BatteryPresentState.present.rawValue.description]
-
- case .discharge:
- return [BatteryDischargeState.unknown.rawValue.description,
- BatteryDischargeState.notSupported.rawValue.description,
- BatteryDischargeState.notDischarging.rawValue.description,
- BatteryDischargeState.discharging.rawValue.description]
-
- case .charge:
- return [BatteryChargeState.unknown.rawValue.description,
- BatteryChargeState.notChargeable.rawValue.description,
- BatteryChargeState.notCharging.rawValue.description,
- BatteryChargeState.charging.rawValue.description]
-
- case .level:
- return [BatteryLevelState.unknown.rawValue.description,
- BatteryLevelState.notSupported.rawValue.description,
- BatteryLevelState.good.rawValue.description,
- BatteryLevelState.criticallyLow.rawValue.description]
- }
- }
-
- var keyboardType: UIKeyboardType { return .default }
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/DateTimeCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/DateTimeCharacteristicViewController.swift
deleted file mode 100644
index 5d61ee8..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/DateTimeCharacteristicViewController.swift
+++ /dev/null
@@ -1,119 +0,0 @@
-//
-// DateTimeCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/25/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class DateTimeCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) weak var dateTimeLabel: UILabel!
-
- @IBOutlet private(set) weak var datePicker: UIDatePicker!
-
- // MARK: - Properties
-
- var value = GATTDateTime(date: Date())
-
- var valueDidChange: ((GATTDateTime) -> ())?
-
- private lazy var minimumDate: Date = {
-
- var dateComponents = DateComponents()
- dateComponents.year = Int(GATTDateTime.Year.min.rawValue)
- dateComponents.timeZone = TimeZone(identifier: "UTC")
-
- guard let minimumDate = calendar.date(from: dateComponents)
- else { fatalError("Couldn't create minimumDate") }
-
- return minimumDate
- }()
-
- private lazy var maximumDate: Date = {
-
- var dateComponents = DateComponents()
- dateComponents.year = Int(GATTDateTime.Year.max.rawValue)
- dateComponents.timeZone = TimeZone(identifier: "UTC")
-
- guard let maximumDate = calendar.date(from: dateComponents)
- else { fatalError("Couldn't create maximumDate") }
-
- return maximumDate
- }()
-
- private lazy var calendar: Calendar = {
-
- return Calendar(identifier: .gregorian)
- }()
-
- private lazy var timeZone: TimeZone = {
-
- guard let timezone = TimeZone(identifier: "UTC")
- else { fatalError("Couldn't create timezone") }
-
- return timezone
- }()
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- datePicker.minimumDate = minimumDate
- datePicker.maximumDate = maximumDate
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func datePickerChanged(_ sender: Any) {
-
- let dateString = CustomDateFormatter.default.string(from: datePicker.date)
- dateTimeLabel.text = dateString
-
- self.value = GATTDateTime(date: datePicker.date)
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateDatePicker()
- }
-
- func updateDatePicker() {
-
- guard let date = value.dateComponents.date
- else { return }
-
- dateTimeLabel.text = CustomDateFormatter.default.string(from: date)
- datePicker.date = date
- }
-
-}
-
-extension DateTimeCharacteristicViewController {
-
- struct CustomDateFormatter {
-
- static let `default`: DateFormatter = {
-
- let formatter = DateFormatter()
- formatter.dateFormat = "MM/dd/yyyy HH:mm:ss"
- return formatter
- }()
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/FirmwareRevisionStringCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/FirmwareRevisionStringCharacteristicViewController.swift
deleted file mode 100644
index 79e9ea0..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/FirmwareRevisionStringCharacteristicViewController.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// FirmwareRevisionStringCharacteristicsViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/25/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class FirmwareRevisionStringCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var firmwareTextField: UITextField!
-
- // MARK: - Properties
-
- var value: GATTFirmwareRevisionString = ""
-
- var valueDidChange: ((GATTFirmwareRevisionString) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func textFieldEditingChanged(_ sender: Any) {
-
- guard let text = firmwareTextField.text
- else { return }
-
- value = GATTFirmwareRevisionString(rawValue: text)
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateText()
- }
-
- private func updateText() {
-
- firmwareTextField.isEnabled = valueDidChange != nil
- firmwareTextField.text = value.rawValue
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/HardwareRevisionStringCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/HardwareRevisionStringCharacteristicViewController.swift
deleted file mode 100644
index 29f8faf..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/HardwareRevisionStringCharacteristicViewController.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// HardwareRevisionStringCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/28/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class HardwareRevisionStringCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var hardwareTextField: UITextField!
-
- // MARK: - Properties
-
- var value: GATTHardwareRevisionString = ""
-
- var valueDidChange: ((GATTHardwareRevisionString) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func textFieldEditingChanged(_ sender: Any) {
-
- guard let text = hardwareTextField.text
- else { return }
-
- value = GATTHardwareRevisionString(rawValue: text)
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateText()
- }
-
- private func updateText() {
-
- hardwareTextField.isEnabled = valueDidChange != nil
- hardwareTextField.text = value.rawValue
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/ManufacturerNameStringCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/ManufacturerNameStringCharacteristicViewController.swift
deleted file mode 100644
index 51833d0..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/ManufacturerNameStringCharacteristicViewController.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// ManufacturerNameStringCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/25/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class ManufacturerNameStringCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var manufacturerNameTextField: UITextField!
-
- // MARK: - Properties
-
- var value: GATTManufacturerNameString = ""
-
- var valueDidChange: ((GATTManufacturerNameString) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func textFieldEditingChanged(_ sender: Any) {
-
- guard let text = manufacturerNameTextField.text
- else { return }
-
- value = GATTManufacturerNameString(rawValue: text)
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateText()
- }
-
- func updateText() {
-
- manufacturerNameTextField.text = value.rawValue
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/ModelNumberCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/ModelNumberCharacteristicViewController.swift
deleted file mode 100644
index c176848..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/ModelNumberCharacteristicViewController.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// ModelNumberCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/25/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class ModelNumberCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var modelTextField: UITextField!
-
- // MARK: - Properties
-
- var value: GATTModelNumber = ""
-
- var valueDidChange: ((GATTModelNumber) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func textFieldEditingChanged(_ sender: UITextField) {
-
- guard let text = modelTextField.text
- else { return }
-
- value = GATTModelNumber(rawValue: text)
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateText()
- }
-
- private func updateText() {
-
- modelTextField.isEnabled = valueDidChange != nil
- modelTextField.text = value.rawValue
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/PnPIDCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/PnPIDCharacteristicViewController.swift
deleted file mode 100644
index a652da3..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/PnPIDCharacteristicViewController.swift
+++ /dev/null
@@ -1,167 +0,0 @@
-//
-// BatteryPowerStateCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 7/2/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import Bluetooth
-
-final class PnPIDCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- typealias VendorIDSource = GATTPnPID.VendorIDSource
-
- // MARK: - Properties
-
- private let cellIdentifier = "InputTextViewCell"
-
- private var fields = [Field]()
-
- var value = GATTPnPID(vendorIdSource: .fromAssignedNumbersDocument, vendorId: 0, productId: 0, productVersion: 0)
-
- var valueDidChange: ((GATTPnPID) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.vendorIdSource("\(value.vendorIdSource)"),
- .vendorId("\(value.vendorId)"),
- .productId("\(value.productId)"),
- .productVersionId("\(value.productVersion)"),]
-
- tableView.register(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.posibleInputValues = field.posibleValues
-// cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.keyboardType = field.keyboardType
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- cell.inputTextView.validator = { value in
-
- guard value.trim() != ""
- else { return .none }
-
- switch field {
- case .vendorId(let value):
-
- guard let _ = UInt16(value)
- else { return .error("Maximum value is 0xFFFF)") }
-
- case .productId(let value):
-
- guard let _ = UInt16(value)
- else { return .error("Maximum value is 0xFFFF)") }
-
- case .productVersionId(let value):
-
- guard let _ = UInt16(value)
- else { return .error("Maximum value is 0xFFFF)") }
-
- default:
- break
- }
-
- return .none
- }
- }
-}
-
-extension PnPIDCharacteristicViewController {
-
- enum Field {
-
- case vendorIdSource(String)
- case vendorId(String)
- case productId(String)
- case productVersionId(String)
-
- var title: String {
-
- switch self {
- case .vendorIdSource:
- return "Vendor ID Source"
-
- case .vendorId:
- return "Vendor ID"
-
- case .productId:
- return "Product ID"
-
- case .productVersionId:
- return "Product Version ID"
- }
- }
-
- var bluetoothValue: String {
- switch self {
- case .vendorIdSource(let value):
- return value
-
- case .vendorId(let value):
- return value
-
- case .productId(let value):
- return value
-
- case .productVersionId(let value):
- return value
- }
- }
-
- var posibleValues: [String] {
- switch self {
- case .vendorIdSource:
- return [VendorIDSource.fromAssignedNumbersDocument.rawValue.description,
- VendorIDSource.fromVendorIDValue.rawValue.description]
-
- default:
- return []
- }
- }
-
- var keyboardType: UIKeyboardType {
- switch self {
- case .vendorIdSource:
- return .default
-
- default:
- return.numberPad
- }
- }
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/SerialNumberStringCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/SerialNumberStringCharacteristicViewController.swift
deleted file mode 100644
index a79cf9d..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/SerialNumberStringCharacteristicViewController.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// SerialNumberStringCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/28/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class SerialNumberStringCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var serialNumberTextField: UITextField!
-
- // MARK: - Properties
-
- var value: GATTSerialNumberString = ""
-
- var valueDidChange: ((GATTSerialNumberString) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func textFieldEditingChanged(_ sender: Any) {
-
- guard let text = serialNumberTextField.text
- else { return }
-
- value = GATTSerialNumberString(rawValue: text)
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateText()
- }
-
- private func updateText() {
-
- serialNumberTextField.isEnabled = valueDidChange != nil
- serialNumberTextField.text = value.rawValue
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/SoftwareRevisionStringCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/SoftwareRevisionStringCharacteristicViewController.swift
deleted file mode 100644
index a665063..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/SoftwareRevisionStringCharacteristicViewController.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// SoftwareRevisionStringCharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/25/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class SoftwareRevisionStringCharacteristicViewController: UIViewController, CharacteristicViewController, InstantiableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var softwareTextField: UITextField!
-
- // MARK: - Properties
-
- var value: GATTSoftwareRevisionString = ""
-
- var valueDidChange: ((GATTSoftwareRevisionString) -> ())?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- configureView()
- }
-
- // MARK: - Actions
-
- @IBAction func textFieldEditingChanged(_ sender: Any) {
-
- guard let text = softwareTextField.text
- else { return }
-
- value = GATTSoftwareRevisionString(rawValue: text)
- valueDidChange?(value)
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- updateText()
- }
-
- private func updateText() {
-
- softwareTextField.isEnabled = valueDidChange != nil
- softwareTextField.text = value.rawValue
- }
-}
diff --git a/BluetoothExplorer/Controller/GATT Characteristic Editors/SystemIDCharacteristicViewController.swift b/BluetoothExplorer/Controller/GATT Characteristic Editors/SystemIDCharacteristicViewController.swift
deleted file mode 100644
index 8547b54..0000000
--- a/BluetoothExplorer/Controller/GATT Characteristic Editors/SystemIDCharacteristicViewController.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-//
-// SystemIDViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/27/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-import Bluetooth
-
-final class SystemIDCharacteristicViewController: UITableViewController, CharacteristicViewController, InstantiableViewController {
-
- private let cellIdentifier = "InputTextViewCell"
-
- // MARK: - Properties
-
- private var fields = [Field]()
-
- var value = GATTSystemID(manufacturerIdentifier: 0, organizationallyUniqueIdentifier: 0)
-
- var valueDidChange: ((GATTSystemID) -> ())?
-
- // MARK: - Life cycle
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- fields = [.manufacturerIdentifier("\(value.manufacturerIdentifier)"),
- .organizationIdentifier("\(value.organizationallyUniqueIdentifier)")]
-
- tableView.register(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier)
- tableView.separatorStyle = .none
- }
-
- // MARK: - UITableViewController
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return fields.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let field = fields[indexPath.row]
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.selectionStyle = .none
-
- configure(cell, field: field)
-
- return cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputTextViewCell
- cell.inputTextView.textField.becomeFirstResponder()
- }
-
- // MARK: - Methods
-
- func configure(_ cell: InputTextViewCell, field: Field) {
-
- cell.inputTextView.value = field.bluetoothValue
- cell.inputTextView.isEnabled = valueDidChange != nil
- cell.inputTextView.keyboardType = .numberPad
- cell.inputTextView.fieldLabelText = field.title
- cell.inputTextView.placeholder = field.title
- cell.inputTextView.validator = { value in
-
- guard value.trim() != ""
- else { return .none }
-
- switch field {
- case .manufacturerIdentifier:
-
- // TODO: Use Bluetooth max value
- guard let manufacturerIdValue = UInt64(value), manufacturerIdValue <= 1099511627775
- else { return .error("Maximum value is \(1099511627775)") }
-
- case .organizationIdentifier:
-
- // TODO: Use Bluetooth max value
- guard let organizationIdvalue = UInt32(value), organizationIdvalue <= 16777215
- else { return .error("Maximum value is \(16777215)") }
- }
-
- return .none
- }
- }
-}
-
-extension SystemIDCharacteristicViewController {
-
- enum Field {
-
- case manufacturerIdentifier(String)
- case organizationIdentifier(String)
-
- var title: String {
-
- switch self {
- case .manufacturerIdentifier:
- return "Manufacturer Identifier"
-
- case .organizationIdentifier:
- return "Organization Identifier"
- }
- }
-
- var bluetoothValue: String {
- switch self {
- case .manufacturerIdentifier(let value):
- return value
-
- case .organizationIdentifier(let value):
- return value
- }
- }
- }
-}
diff --git a/BluetoothExplorer/Controller/PeripheralCharacteristicDetailViewController.swift b/BluetoothExplorer/Controller/PeripheralCharacteristicDetailViewController.swift
deleted file mode 100644
index 4455faa..0000000
--- a/BluetoothExplorer/Controller/PeripheralCharacteristicDetailViewController.swift
+++ /dev/null
@@ -1,395 +0,0 @@
-//
-// PeripheralCharacteristicDetailViewController.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/20/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class PeripheralCharacteristicDetailViewController: UITableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var activityIndicatorBarButtonItem: UIBarButtonItem!
-
- // MARK: - Properties
-
- var characteristic: CharacteristicManagedObject! {
-
- didSet { configureView() }
- }
-
- var value: Data? {
-
- didSet { configureHexadecimalTextField() }
- }
-
- private var dataSource = [Section]()
-
- private lazy var cellCache: CellCache = CellCache(tableView: tableView)
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- hideActivity(animated: false)
- cellCache.hexadecimalCell.textField.delegate = self
- configureView()
-
- // automatically read value if supported
- if self.characteristic.attributesView.properties.contains(.read) {
-
- readValue()
- }
- }
-
- // MARK: - Methods
-
- private func configureView() {
-
- guard isViewLoaded else { return }
-
- guard let managedObject = self.characteristic
- else { fatalError("View controller not configured") }
-
- let characteristic = managedObject.attributesView
-
- self.title = characteristic.uuid.name ?? characteristic.uuid.rawValue
-
- let canWrite = characteristic.properties.contains(.write)
- || characteristic.properties.contains(.writeWithoutResponse)
-
- self.dataSource = []
- self.dataSource.reserveCapacity(2)
-
- // value cell
- var valueSection = Section(title: "Value", items: [cellCache.hexadecimalCell])
- self.loadValue()
-
- // editor cell
- if supportedCharacteristicViewControllers.contains(characteristic.uuid) {
-
- let name = characteristic.uuid.name ?? characteristic.uuid.rawValue
-
- let editCell = cellCache.editCell
-
- let editText: String
-
- if canWrite {
-
- editText = "Edit " + name
-
- } else {
-
- editText = "View " + name
- }
-
- editCell.textLabel?.text = editText
-
- valueSection.items.append(editCell)
- }
-
- self.dataSource.append(valueSection)
-
- // properties
- var propertiesSection = Section(title: "Properties", items: [])
-
- if characteristic.properties.contains(.read) {
-
- propertiesSection.items.append(cellCache.readValueCell)
- }
-
- if characteristic.properties.contains(.write) {
-
- propertiesSection.items.append(cellCache.writeValueCell)
- }
-
- if characteristic.properties.contains(.writeWithoutResponse) {
-
- propertiesSection.items.append(cellCache.writeWithoutResponseCell)
- }
-
- if propertiesSection.items.isEmpty == false {
-
- self.dataSource.append(propertiesSection)
- }
-
- tableView.reloadData()
- }
-
- private func loadValue() {
-
- self.value = self.characteristic.value
- }
-
- private func configureHexadecimalTextField() {
-
- cellCache.hexadecimalCell.textField.text = self.value?.toHexadecimal() ?? ""
- }
-
- private func editViewController() -> UIViewController? {
-
- let characteristic = self.characteristic.attributesView
-
- let canWrite = characteristic.properties.contains(.write)
- || characteristic.properties.contains(.writeWithoutResponse)
-
- func load (_ type: T.Type) -> T {
-
- let viewController: T = .instantiate()
-
- if let data = self.value, let value = T.CharacteristicValue(data: data) {
-
- viewController.value = value
- }
-
- if canWrite {
-
- viewController.valueDidChange = { [weak self] in self?.value = $0.data }
- }
-
- return viewController
- }
-
- let viewController: UIViewController?
-
- switch characteristic.uuid {
-
- case BatteryLevelCharacteristicViewController.uuid:
- viewController = load(BatteryLevelCharacteristicViewController.self)
-
- case ModelNumberCharacteristicViewController.uuid:
- viewController = load(ModelNumberCharacteristicViewController.self)
-
- case FirmwareRevisionStringCharacteristicViewController.uuid:
- viewController = load(FirmwareRevisionStringCharacteristicViewController.self)
-
- case SoftwareRevisionStringCharacteristicViewController.uuid:
- viewController = load(SoftwareRevisionStringCharacteristicViewController.self)
-
- case ManufacturerNameStringCharacteristicViewController.uuid:
- viewController = load(ManufacturerNameStringCharacteristicViewController.self)
-
- case DateTimeCharacteristicViewController.uuid:
- viewController = load(DateTimeCharacteristicViewController.self)
-
- case SystemIDCharacteristicViewController.uuid:
- viewController = load(SystemIDCharacteristicViewController.self)
-
- case PnPIDCharacteristicViewController.uuid:
- viewController = load(PnPIDCharacteristicViewController.self)
-
- case HardwareRevisionStringCharacteristicViewController.uuid:
- viewController = load(HardwareRevisionStringCharacteristicViewController.self)
-
- case SerialNumberStringCharacteristicViewController.uuid:
- viewController = load(SerialNumberStringCharacteristicViewController.self)
-
- case AlertCategoryCharacteristicViewController.uuid:
- viewController = load(AlertCategoryCharacteristicViewController.self)
-
- case AlertLevelCharacteristicViewController.uuid:
- viewController = load(AlertLevelCharacteristicViewController.self)
-
- case BatteryPowerStateCharacteristicViewController.uuid:
- viewController = load(BatteryPowerStateCharacteristicViewController.self)
-
- case AlertNotificationControlPointCharacteristicViewController.uuid:
- viewController = load(AlertNotificationControlPointCharacteristicViewController.self)
-
- case AgeCharacteristicViewController.uuid:
- viewController = load(AgeCharacteristicViewController.self)
-
- case AerobicHeartRateLowerLimitCharacteristicViewController.uuid:
- viewController = load(AerobicHeartRateLowerLimitCharacteristicViewController.self)
-
- case AerobicHeartRateUpperLimitCharacteristicViewController.uuid:
- viewController = load(AerobicHeartRateUpperLimitCharacteristicViewController.self)
-
- default:
- viewController = nil
- }
-
- return viewController
- }
-
- private func edit() {
-
- guard let viewController = editViewController()
- else { assertionFailure("Could not initialize editor view controller"); return }
-
- show(viewController, sender: self)
- }
-
- private func readValue() {
-
- performActivity({
- try DeviceStore.shared.readValue(for: self.characteristic)
- }, completion: { (viewController, _) in
- viewController.loadValue()
- })
- }
-
- private func writeValue(withResponse: Bool = true) {
-
- let data = self.value ?? Data()
-
- performActivity({
- try DeviceStore.shared.writeValue(data, withResponse: withResponse, for: self.characteristic)
- })
- }
-
- // MARK: - UITableViewDataSource
-
- override func numberOfSections(in tableView: UITableView) -> Int {
-
- return dataSource.count
- }
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-
- return dataSource[section].items.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let cell = dataSource[indexPath.section].items[indexPath.row]
-
- return cell
- }
-
- // MARK: - UITableViewDelegate
-
- override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
-
- let section = self.dataSource[section]
-
- return section.title
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-
- defer { tableView.deselectRow(at: indexPath, animated: true) }
-
- let section = self.dataSource[indexPath.section]
-
- let cell = section.items[indexPath.row]
-
- guard let cellIdentifier = Cell(rawValue: cell.reuseIdentifier ?? "")
- else { assertionFailure("Invalid cell"); return }
-
- switch cellIdentifier {
-
- case .hexadecimal:
- break
-
- case .edit:
- edit()
-
- case .read:
- readValue()
-
- case .write:
- writeValue()
-
- case .writeWithoutResponse:
- writeValue(withResponse: false)
- }
- }
-}
-
-extension PeripheralCharacteristicDetailViewController: UITextFieldDelegate {
-
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-
- textField.resignFirstResponder()
- return false
- }
-
- func textFieldDidBeginEditing(_ textField: UITextField) {
-
-
- }
-}
-
-extension PeripheralCharacteristicDetailViewController: ActivityIndicatorViewController {
-
- func showActivity() {
-
- self.view.endEditing(true)
-
- self.activityIndicatorBarButtonItem.customView?.alpha = 1.0
- }
-
- func hideActivity(animated: Bool = true) {
-
- let duration: TimeInterval = animated ? 0.5 : 0.0
-
- UIView.animate(withDuration: duration) { [weak self] in
-
- self?.activityIndicatorBarButtonItem.customView?.alpha = 0.0
- }
- }
-}
-
-// MARK: - Supporting Types
-
-private extension PeripheralCharacteristicDetailViewController {
-
- enum Cell: String {
-
- case hexadecimal = "HexadecimalValueTableViewCell"
-
- case edit = "EditCell"
-
- case read = "ReadValueCell"
-
- case write = "WriteValueCell"
-
- case writeWithoutResponse = "WriteWithoutResponseCell"
- }
-
- struct Section {
-
- let title: String?
-
- var items: [UITableViewCell]
- }
-
- final class CellCache {
-
- init(tableView: UITableView) {
-
- self.tableView = tableView
- }
-
- private(set) weak var tableView: UITableView!
-
- private func dequeueReusableCell (_ cell: Cell) -> T {
-
- return tableView.dequeueReusableCell(withIdentifier: cell.rawValue) as! T
- }
-
- lazy var hexadecimalCell: HexadecimalValueTableViewCell = self.dequeueReusableCell(.hexadecimal)
-
- lazy var editCell: UITableViewCell = self.dequeueReusableCell(.edit)
-
- lazy var readValueCell: UITableViewCell = self.dequeueReusableCell(.read)
-
- lazy var writeValueCell: UITableViewCell = self.dequeueReusableCell(.write)
-
- lazy var writeWithoutResponseCell: UITableViewCell = self.dequeueReusableCell(.writeWithoutResponse)
- }
-}
-
-final class HexadecimalValueTableViewCell: UITableViewCell {
-
- @IBOutlet private(set) var textField: UITextField!
-}
diff --git a/BluetoothExplorer/Controller/PeripheralCharacteristicsViewController.swift b/BluetoothExplorer/Controller/PeripheralCharacteristicsViewController.swift
deleted file mode 100644
index bbe4123..0000000
--- a/BluetoothExplorer/Controller/PeripheralCharacteristicsViewController.swift
+++ /dev/null
@@ -1,167 +0,0 @@
-//
-// PeripheralCharacteristicsViewController.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/20/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class PeripheralCharacteristicsViewController: TableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var activityIndicatorBarButtonItem: UIBarButtonItem!
-
- // MARK: - Properties
-
- public var service: ServiceManagedObject!
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- reloadData()
- }
-
- // MARK: - Actions
-
- @IBAction func pullToRefresh(_ sender: UIRefreshControl) {
-
- reloadData()
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- guard let managedObject = self.service
- else { fatalError("View controller not configured") }
-
- let service = Service(managedObject: managedObject)
-
- self.title = service.uuid.name ?? service.uuid.rawValue
- }
-
- func reloadData() {
-
- guard let managedObject = self.service
- else { fatalError("View controller not configured") }
-
- configureView()
-
- let isRefreshing = self.refreshControl?.isRefreshing ?? false
- let showActivity = isRefreshing == false
-
- performActivity(showActivity: showActivity, {
- try DeviceStore.shared.discoverCharacteristics(for: managedObject)
- })
- }
-
- override func newFetchedResultController() -> NSFetchedResultsController {
-
- guard let managedObject = self.service
- else { fatalError("View controller not configured") }
-
- // configure fetched results controller
- let predicate = NSPredicate(format: "%K == %@",
- #keyPath(CharacteristicManagedObject.service),
- managedObject)
-
- let sort = [NSSortDescriptor(key: #keyPath(CharacteristicManagedObject.uuid), ascending: true)]
- let context = DeviceStore.shared.managedObjectContext
- let fetchedResultsController = NSFetchedResultsController(CharacteristicManagedObject.self,
- delegate: self,
- predicate: predicate,
- sortDescriptors: sort,
- context: context)
- fetchedResultsController.fetchRequest.fetchBatchSize = 30
-
- return fetchedResultsController
- }
-
- private subscript (indexPath: IndexPath) -> CharacteristicManagedObject {
-
- guard let managedObject = self.fetchedResultsController?.object(at: indexPath) as? CharacteristicManagedObject
- else { fatalError("Invalid type") }
-
- return managedObject
- }
-
- private func configure(cell: UITableViewCell, at indexPath: IndexPath) {
-
- let managedObject = self[indexPath]
-
- let attributes = managedObject.attributesView
-
- if let name = attributes.uuid.name {
-
- cell.textLabel?.text = name
- cell.detailTextLabel?.text = attributes.uuid.rawValue
-
- } else {
-
- cell.textLabel?.text = attributes.uuid.rawValue
- cell.detailTextLabel?.text = ""
- }
- }
-
- // MARK: - UITableViewDataSource
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: "CharacteristicCell", for: indexPath)
-
- configure(cell: cell, at: indexPath)
-
- return cell
- }
-
- // MARK: - Segue
-
- override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
-
- let identifier = segue.identifier ?? ""
-
- switch identifier {
-
- case "showPeripheralCharacteristic":
-
- let viewController = segue.destination as! PeripheralCharacteristicDetailViewController
- viewController.characteristic = self[tableView.indexPathForSelectedRow!]
-
- default:
- assertionFailure("Unknown segue \(segue)")
- }
- }
-}
-
-// MARK: - ActivityIndicatorViewController
-
-extension PeripheralCharacteristicsViewController: ActivityIndicatorViewController {
-
- func showActivity() {
-
- self.view.endEditing(true)
-
- self.activityIndicatorBarButtonItem.customView?.alpha = 1.0
- }
-
- func hideActivity(animated: Bool = true) {
-
- let duration: TimeInterval = animated ? 0.5 : 0.0
-
- UIView.animate(withDuration: duration) { [weak self] in
-
- self?.activityIndicatorBarButtonItem.customView?.alpha = 0.0
- }
- }
-}
diff --git a/BluetoothExplorer/Controller/PeripheralServicesViewController.swift b/BluetoothExplorer/Controller/PeripheralServicesViewController.swift
deleted file mode 100644
index b40c3e5..0000000
--- a/BluetoothExplorer/Controller/PeripheralServicesViewController.swift
+++ /dev/null
@@ -1,172 +0,0 @@
-//
-// PeripheralServicesViewController.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/20/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class PeripheralServicesViewController: TableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var activityIndicatorBarButtonItem: UIBarButtonItem!
-
- // MARK: - Properties
-
- public var peripheral: PeripheralManagedObject!
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- reloadData()
- }
-
- // MARK: - Actions
-
- @IBAction func pullToRefresh(_ sender: UIRefreshControl) {
-
- reloadData()
- }
-
- // MARK: - Methods
-
- func configureView() {
-
- guard isViewLoaded else { return }
-
- guard let managedObject = self.peripheral
- else { assertionFailure(); return }
-
- self.title = managedObject.scanData.advertisementData.localName ?? managedObject.identifier
- }
-
- func reloadData() {
-
- guard let peripheral = self.peripheral
- else { fatalError("View controller not configured") }
-
- configureView()
- performActivity({ try DeviceStore.shared.discoverServices(for: peripheral) })
- }
-
- override func newFetchedResultController() -> NSFetchedResultsController {
-
- guard let peripheral = self.peripheral
- else { fatalError("View controller not configured") }
-
- // configure fetched results controller
- let predicate = NSPredicate(format: "%K == %@",
- #keyPath(ServiceManagedObject.peripheral),
- peripheral)
-
- let sort = [NSSortDescriptor(key: #keyPath(ServiceManagedObject.uuid), ascending: true)]
- let context = DeviceStore.shared.managedObjectContext
- let fetchedResultsController = NSFetchedResultsController(ServiceManagedObject.self,
- delegate: self,
- predicate: predicate,
- sortDescriptors: sort,
- context: context)
- fetchedResultsController.fetchRequest.fetchBatchSize = 30
-
- return fetchedResultsController
- }
-
- private subscript (indexPath: IndexPath) -> ServiceManagedObject {
-
- guard let managedObject = self.fetchedResultsController?.object(at: indexPath) as? ServiceManagedObject
- else { fatalError("Invalid type") }
-
- return managedObject
- }
-
- private func configure(cell: UITableViewCell, at indexPath: IndexPath) {
-
- let managedObject = self[indexPath]
-
- let attributes = managedObject.attributesView
-
- if let name = attributes.uuid.name {
-
- cell.textLabel?.text = name
- cell.detailTextLabel?.text = attributes.uuid.rawValue
-
- } else {
-
- cell.textLabel?.text = attributes.uuid.rawValue
- cell.detailTextLabel?.text = ""
- }
- }
-
- // MARK: - UITableViewDataSource
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: "ServiceCell", for: indexPath)
-
- configure(cell: cell, at: indexPath)
-
- return cell
- }
-
- // MARK: - Segue
-
- override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
-
- let identifier = segue.identifier ?? ""
-
- switch identifier {
-
- case "showPeripheralCharacteristics":
-
- let viewController = segue.destination as! PeripheralCharacteristicsViewController
- viewController.service = self[tableView.indexPathForSelectedRow!]
-
- default: assertionFailure("Unknown segue \(segue)")
- }
- }
-}
-
-// MARK: - ActivityIndicatorViewController
-
-extension PeripheralServicesViewController: ActivityIndicatorViewController {
-
- func showActivity() {
-
- self.view.endEditing(true)
-
- let isRefreshing = self.refreshControl?.isRefreshing ?? false
-
- if isRefreshing == false {
-
- self.activityIndicatorBarButtonItem.customView?.alpha = 1.0
- }
- }
-
- func hideActivity(animated: Bool = true) {
-
- let isRefreshing = self.refreshControl?.isRefreshing ?? false
-
- if isRefreshing {
-
- self.endRefreshing()
-
- } else {
-
- let duration: TimeInterval = animated ? 0.5 : 0.0
-
- UIView.animate(withDuration: duration) { [weak self] in
-
- self?.activityIndicatorBarButtonItem.customView?.alpha = 0.0
- }
- }
- }
-}
diff --git a/BluetoothExplorer/Controller/PeripheralsViewController.swift b/BluetoothExplorer/Controller/PeripheralsViewController.swift
deleted file mode 100644
index 51c3dc0..0000000
--- a/BluetoothExplorer/Controller/PeripheralsViewController.swift
+++ /dev/null
@@ -1,170 +0,0 @@
-//
-// ViewController.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/19/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-import Bluetooth
-import GATT
-
-final class PeripheralsViewController: TableViewController {
-
- // MARK: - IB Outlets
-
- @IBOutlet private(set) var activityIndicatorBarButtonItem: UIBarButtonItem!
-
- // MARK: - Properties
-
- let scanDuration: TimeInterval = 5.0
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
-
- }
-
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
-
- reloadData()
- }
-
- // MARK: - Actions
-
- @IBAction func pullToRefresh(_ sender: UIRefreshControl) {
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { [weak self] in self?.reloadData() })
- }
-
- // MARK: - Methods
-
- func reloadData() {
-
- // scan
- let scanDuration = self.scanDuration
-
- performActivity({ try DeviceStore.shared.scan(duration: scanDuration) })
- }
-
- override func newFetchedResultController() -> NSFetchedResultsController {
-
- // configure fetched results controller
- let predicate = NSPredicate(format: "%K == %@",
- #keyPath(PeripheralManagedObject.isAvailible),
- true as NSNumber)
-
- let sort = [
- NSSortDescriptor(key: #keyPath(PeripheralManagedObject.scanData.advertisementData.localName),
- ascending: true),
- NSSortDescriptor(key: #keyPath(PeripheralManagedObject.identifier),
- ascending: true)
- ]
-
- let context = DeviceStore.shared.managedObjectContext
-
- let fetchedResultsController = NSFetchedResultsController(PeripheralManagedObject.self,
- delegate: self,
- predicate: predicate,
- sortDescriptors: sort,
- context: context)
-
- fetchedResultsController.fetchRequest.fetchBatchSize = 20
-
- return fetchedResultsController
- }
-
- private subscript (indexPath: IndexPath) -> PeripheralManagedObject {
-
- guard let managedObject = self.fetchedResultsController?.object(at: indexPath) as? PeripheralManagedObject
- else { fatalError("Invalid type") }
-
- return managedObject
- }
-
- private func configure(cell: UITableViewCell, at indexPath: IndexPath) {
-
- let peripheral = self[indexPath]
-
- if let localName = peripheral.scanData.advertisementData.localName {
-
- cell.textLabel?.text = localName
- cell.detailTextLabel?.text = peripheral.identifier
-
- } else {
-
- cell.textLabel?.text = peripheral.identifier
- cell.detailTextLabel?.text = ""
- }
- }
-
- // MARK: - UITableViewDataSource
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
- let cell = tableView.dequeueReusableCell(withIdentifier: "PeripheralCell", for: indexPath)
-
- configure(cell: cell, at: indexPath)
-
- return cell
- }
-
- // MARK: - Segue
-
- override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
-
- let identifier = segue.identifier ?? ""
-
- switch identifier {
-
- case "showPeripheralDetail":
-
- let viewController = segue.destination as! PeripheralServicesViewController
- viewController.peripheral = self[tableView.indexPathForSelectedRow!]
-
- default: assertionFailure("Unknown segue \(segue)")
- }
- }
-}
-
-// MARK: - ActivityIndicatorViewController
-
-extension PeripheralsViewController: ActivityIndicatorViewController {
-
- func showActivity() {
-
- self.tableView.scrollRectToVisible(.zero, animated: true)
-
- let isRefreshing = self.refreshControl?.isRefreshing ?? false
-
- if isRefreshing == false {
-
- self.activityIndicatorBarButtonItem.customView?.alpha = 1.0
- }
- }
-
- func hideActivity(animated: Bool = true) {
-
- let isRefreshing = self.refreshControl?.isRefreshing ?? false
-
- if isRefreshing {
-
- self.endRefreshing()
-
- } else {
-
- let duration: TimeInterval = animated ? 0.5 : 0.0
-
- UIView.animate(withDuration: duration) { [weak self] in
-
- self?.activityIndicatorBarButtonItem.customView?.alpha = 0.0
- }
- }
- }
-}
diff --git a/BluetoothExplorer/Controller/Protocols/CharacteristicViewController.swift b/BluetoothExplorer/Controller/Protocols/CharacteristicViewController.swift
deleted file mode 100644
index 8a46e12..0000000
--- a/BluetoothExplorer/Controller/Protocols/CharacteristicViewController.swift
+++ /dev/null
@@ -1,54 +0,0 @@
-//
-// CharacteristicViewController.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/20/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import Bluetooth
-
-protocol CharacteristicViewController: class {
-
- /// The GATT Characteristic type this view controller can edit.
- associatedtype CharacteristicValue: GATTCharacteristic
-
- var view: UIView! { get }
-
- /// The current value.
- var value: CharacteristicValue { set get }
-
- /// Value changed closure.
- var valueDidChange: ((CharacteristicValue) -> ())? { get set }
-}
-
-extension CharacteristicViewController {
-
- /// The UUID of the Bluetooth Characteristic this view controller can edit.
- static var uuid: BluetoothUUID {
-
- return CharacteristicValue.uuid
- }
-}
-
-let supportedCharacteristicViewControllers: [BluetoothUUID] = [
- .batteryLevel,
- .manufacturerNameString,
- .modelNumberString,
- .dateTime,
- .firmwareRevisionString,
- .softwareRevisionString,
- .hardwareRevisionString,
- .serialNumberString,
- .systemId,
- .pnpId,
- .alertCategoryId,
- .alertLevel,
- .batteryPowerState,
- .alertNotificationControlPoint,
- .age,
- .aerobicHeartRateLowerLimit,
- .aerobicHeartRateUpperLimit
-]
diff --git a/BluetoothExplorer/Controller/Protocols/InstantiableViewController.swift b/BluetoothExplorer/Controller/Protocols/InstantiableViewController.swift
deleted file mode 100644
index b1eea90..0000000
--- a/BluetoothExplorer/Controller/Protocols/InstantiableViewController.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// InstantiableViewController.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/26/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-
-protocol InstantiableViewController {
-
- static func instantiate() -> T
-}
-
-extension InstantiableViewController where Self: UIViewController {
-
- static func instantiate() -> T {
- guard let storyboardName = String(describing: self).text(before: "ViewController") else {
- fatalError("The controller name is not standard.")
- }
-
- let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle(for: self))
- let identifier = String(describing: T.self)
- guard let viewController = storyboard.instantiateViewController(withIdentifier: identifier) as? T else {
- fatalError("The storyboard identifier does not exist.")
- }
-
- return viewController
- }
-
-}
diff --git a/BluetoothExplorer/Controller/TableViewController.swift b/BluetoothExplorer/Controller/TableViewController.swift
deleted file mode 100644
index eb22f7f..0000000
--- a/BluetoothExplorer/Controller/TableViewController.swift
+++ /dev/null
@@ -1,155 +0,0 @@
-//
-// TableViewController.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/19/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-import CoreData
-
-/// Base table view controller
-class TableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
-
- // MARK: - Properties
-
- final private(set) var fetchedResultsController: NSFetchedResultsController?
-
- // MARK: - Loading
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- // setup table view
- self.tableView.estimatedRowHeight = UITableViewAutomaticDimension
- self.tableView.rowHeight = UITableViewAutomaticDimension
-
- // update table view
- self.performFetch()
- }
-
- // MARK: - Methods
-
- /// Initialize `NSFetchedResultsController`.
- final func performFetch() {
-
- tableView.isUserInteractionEnabled = false
- defer { tableView.isUserInteractionEnabled = true }
-
- // remove old FRC
- fetchedResultsController = nil
-
- // setup new FRC
- fetchedResultsController = newFetchedResultController()
-
- do { try fetchedResultsController?.performFetch() }
- catch { assertionFailure("\(error)") }
-
- // reload table view
- tableView.reloadData()
- }
-
- /// Create a new `NSFetchedResultsController` instance.
- func newFetchedResultController() -> NSFetchedResultsController {
-
- fatalError("\(newFetchedResultController) - Subclasses must override this implementation")
- }
-
- final func endRefreshing() {
-
- if let refreshControl = self.refreshControl,
- refreshControl.isRefreshing == true {
-
- refreshControl.endRefreshing()
- }
- }
-
- // MARK: - UITableViewDataSource
-
- final override func numberOfSections(in tableView: UITableView) -> Int {
-
- return self.fetchedResultsController?.sections?.count ?? 0
- }
-
- final override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-
- return self.fetchedResultsController?.sections?[section].numberOfObjects ?? 0
- }
-
- override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
-
- guard let sections = self.fetchedResultsController?.sections
- else { return nil }
-
- return sections[section].name
- }
-
- #if os(iOS)
- override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
-
- return self.fetchedResultsController?.section(forSectionIndexTitle: title, at: index)
- ?? super.tableView(tableView, sectionForSectionIndexTitle: title, at: index)
- }
- #endif
-
- // MARK: - NSFetchedResultsControllerDelegate
-
- final func controllerWillChangeContent(_ controller: NSFetchedResultsController) {
-
- self.tableView.beginUpdates()
- }
-
- final func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
-
- self.tableView.endUpdates()
- }
-
- final func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
-
- switch type {
- case .insert:
-
- if let insertIndexPath = newIndexPath {
- self.tableView.insertRows(at: [insertIndexPath], with: .fade)
- }
- case .delete:
-
- if let deleteIndexPath = indexPath {
- self.tableView.deleteRows(at: [deleteIndexPath], with: .fade)
- }
- case .update:
- if let updateIndexPath = indexPath,
- let _ = self.tableView.cellForRow(at: updateIndexPath) {
-
- self.tableView.reloadRows(at: [updateIndexPath], with: .none)
- }
- case .move:
-
- if let deleteIndexPath = indexPath {
- self.tableView.deleteRows(at: [deleteIndexPath], with: .fade)
- }
-
- if let insertIndexPath = newIndexPath {
- self.tableView.insertRows(at: [insertIndexPath], with: .fade)
- }
- }
- }
-
- final func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
-
- switch type {
-
- case .insert:
-
- self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
-
- case .delete:
-
- self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
-
- default: break
- }
- }
-}
diff --git a/BluetoothExplorer/DateTimeCharacteristic.storyboard b/BluetoothExplorer/DateTimeCharacteristic.storyboard
deleted file mode 100644
index 977a959..0000000
--- a/BluetoothExplorer/DateTimeCharacteristic.storyboard
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/Extensions/Data.swift b/BluetoothExplorer/Extensions/Data.swift
deleted file mode 100644
index e6b1976..0000000
--- a/BluetoothExplorer/Extensions/Data.swift
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Data.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/20/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import Bluetooth
-
-internal extension Data {
-
- enum HexEncodingOption: Int, BitMaskOption {
-
- case upperCase = 0b01
-
- static let all: Set = [.upperCase]
- }
-
- func toHexadecimal(options: BitMaskOptionSet = []) -> String {
- let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
- return map { String(format: format, $0) }.joined()
- }
-
- init?(hexadecimal string: String) {
-
- /*
- let elements = Array(string.utf8)
-
- for index in stride(from: 0, to: string.count, by: 2) {
-
- guard let byte = UInt8(String(elements[index ..< index.advanced(by: 2)]), radix: 16)
- else { return nil }
-
-
- }*/
-
- return nil
- }
-}
diff --git a/BluetoothExplorer/Extensions/GATTAlertCategory.swift b/BluetoothExplorer/Extensions/GATTAlertCategory.swift
deleted file mode 100644
index 6051591..0000000
--- a/BluetoothExplorer/Extensions/GATTAlertCategory.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// GATTAlertCategory.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/28/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import Bluetooth
-import Rswift
-
-extension GATTAlertCategory {
-
- var name: String {
-
- switch self {
- case .simpleAlert:
- return R.string.localizable.gattAlertCategorySimpleAlert()
-
- case .email:
- return R.string.localizable.gattAlertCategoryEmail()
-
- case .news:
- return R.string.localizable.gattAlertCategoryNews()
-
- case .call:
- return R.string.localizable.gattAlertCategoryCall()
-
- case .missedCall:
- return R.string.localizable.gattAlertCategoryMissedCall()
-
- case .sms:
- return R.string.localizable.gattAlertCategorySMS()
-
- case .voiceMail:
- return R.string.localizable.gattAlertCategoryVoiceMail()
-
- case .schedule:
- return R.string.localizable.gattAlertCategorySchedule()
-
- case .highPrioritizedAlert:
- return R.string.localizable.gattAlertCategoryHighPrioritized()
-
- case .instantMessage:
- return R.string.localizable.gattAlertCategoryInstantMessage()
- }
- }
-
-}
diff --git a/BluetoothExplorer/Extensions/Integer.swift b/BluetoothExplorer/Extensions/Integer.swift
deleted file mode 100644
index fbece33..0000000
--- a/BluetoothExplorer/Extensions/Integer.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// Integer.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/28/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-
-internal extension UInt8 {
-
- func toHexadecimal() -> String {
-
- var string = String(self, radix: 16)
-
- if string.utf8.count == 1 {
-
- string = "0" + string
- }
-
- return string.uppercased()
- }
-}
diff --git a/BluetoothExplorer/Extensions/String.swift b/BluetoothExplorer/Extensions/String.swift
deleted file mode 100644
index c10731e..0000000
--- a/BluetoothExplorer/Extensions/String.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// String.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/26/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-
-public extension String {
-
- func text(before text: String) -> String? {
-
- guard let range = self.range(of: text) else { return nil }
- return String(self[self.startIndex.. String {
-
- return self.trimmingCharacters(in: CharacterSet.whitespaces)
- }
-}
diff --git a/BluetoothExplorer/FirmwareRevisionStringCharacteristic.storyboard b/BluetoothExplorer/FirmwareRevisionStringCharacteristic.storyboard
deleted file mode 100644
index b1d93d1..0000000
--- a/BluetoothExplorer/FirmwareRevisionStringCharacteristic.storyboard
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/HardwareRevisionStringCharacteristic.storyboard b/BluetoothExplorer/HardwareRevisionStringCharacteristic.storyboard
deleted file mode 100644
index f2dcabf..0000000
--- a/BluetoothExplorer/HardwareRevisionStringCharacteristic.storyboard
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/ManufacturerNameStringCharacteristic.storyboard b/BluetoothExplorer/ManufacturerNameStringCharacteristic.storyboard
deleted file mode 100644
index 0f262f2..0000000
--- a/BluetoothExplorer/ManufacturerNameStringCharacteristic.storyboard
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/Model/AdvertisementDataManagedObject.swift b/BluetoothExplorer/Model/AdvertisementDataManagedObject.swift
deleted file mode 100644
index 5f3e475..0000000
--- a/BluetoothExplorer/Model/AdvertisementDataManagedObject.swift
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// AdvertisementDataManagedObject.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/19/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-import Bluetooth
-import GATT
-
-public final class AdvertisementDataManagedObject: NSManagedObject {
-
- // MARK: - Attributes
-
- @NSManaged
- public var isConnectable: NSNumber? // Bool
-
- @NSManaged
- public var localName: String?
-
- @NSManaged
- public var manufacturerData: Data?
-
- @NSManaged
- public var txPowerLevel: NSNumber? // Double
-
- // MARK: - Relationships
-
- @NSManaged
- public var scanData: ScanDataManagedObject
-}
-
-// MARK: - Encodable
-
-public extension AdvertisementDataManagedObject {
-
- func update(_ value: AdvertisementData) {
-
- self.isConnectable = value.isConnectable as NSNumber?
- self.localName = value.localName
- self.manufacturerData = value.manufacturerData
- self.txPowerLevel = value.txPowerLevel as NSNumber?
- }
-}
-
-// MARK: - Decodable
-
-extension AdvertisementData: CoreDataDecodable {
-
- public init(managedObject: AdvertisementDataManagedObject) {
-
- self.isConnectable = managedObject.isConnectable?.boolValue
- self.localName = managedObject.localName
- self.manufacturerData = managedObject.manufacturerData
- self.txPowerLevel = managedObject.txPowerLevel?.doubleValue
-
- // TODO: Implement other properties
- self.serviceData = [:]
- self.serviceUUIDs = []
- self.overflowServiceUUIDs = []
- self.solicitedServiceUUIDs = []
- }
-}
diff --git a/BluetoothExplorer/Model/CentralManagedObject.swift b/BluetoothExplorer/Model/CentralManagedObject.swift
deleted file mode 100644
index 9a4d772..0000000
--- a/BluetoothExplorer/Model/CentralManagedObject.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// CentralManagedObject.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/15/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-
-/// CoreData managed object for a Bluetooth Central Manager (should be a single instance).
-public final class CentralManagedObject: NSManagedObject {
-
- // MARK: - Attributes
-
- @NSManaged
- public var identifier: String
-
- @NSManaged
- public var connectionTimeout: Double
-
- @NSManaged
- public var isScanning: Bool
-
- @NSManaged
- public var state: Int16
-
- // MARK: - Relationships
-
- @NSManaged
- public var foundDevices: Set
-}
-
-// MARK: - Fetch Requests
-
-extension CentralManagedObject {
-
- static func findOrCreate(_ identifier: String,
- in context: NSManagedObjectContext) throws -> CentralManagedObject {
-
- let identifier = identifier as NSString
-
- let identifierProperty = #keyPath(CentralManagedObject.identifier)
-
- let entityName = self.entity(in: context).name!
-
- return try context.findOrCreate(identifier: identifier, property: identifierProperty, entityName: entityName)
- }
-}
diff --git a/BluetoothExplorer/Model/CharacteristicManagedObject.swift b/BluetoothExplorer/Model/CharacteristicManagedObject.swift
deleted file mode 100644
index e0d0356..0000000
--- a/BluetoothExplorer/Model/CharacteristicManagedObject.swift
+++ /dev/null
@@ -1,104 +0,0 @@
-//
-// CharacteristicManagedObject.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/18/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-import Bluetooth
-import GATT
-
-///
-public final class CharacteristicManagedObject: NSManagedObject {
-
- // MARK: - Attributes
-
- @NSManaged
- public var uuid: String
-
- @NSManaged
- public var properties: Int16 // really `UInt8`
-
- @NSManaged
- public var value: Data?
-
- // MARK: - Relationships
-
- @NSManaged
- public var service: ServiceManagedObject
-}
-
-// MARK: - Computed Properties
-
-public extension CharacteristicManagedObject {
-
- public struct AttributesView {
-
- public typealias Property = GATT.CharacteristicProperty
-
- public var uuid: BluetoothUUID
-
- public var properties: BitMaskOptionSet
-
- public var value: Data?
- }
-
- public var attributesView: AttributesView {
-
- guard let uuid = BluetoothUUID(rawValue: self.uuid)
- else { fatalError("Invalid stored value \(self.uuid)") }
-
- let properties = BitMaskOptionSet(rawValue: UInt8(self.properties))
-
- return AttributesView(uuid: uuid,
- properties: properties,
- value: self.value)
- }
-}
-
-// MARK: - Fetch Requests
-
-extension CharacteristicManagedObject {
-
- static func find(_ uuid: BluetoothUUID,
- service: ServiceManagedObject,
- in context: NSManagedObjectContext) throws -> CharacteristicManagedObject? {
-
- let entityName = self.entity(in: context).name!
- let fetchRequest = NSFetchRequest(entityName: entityName)
- fetchRequest.predicate = NSPredicate(format: "%K == %@ && %K == %@",
- #keyPath(CharacteristicManagedObject.uuid),
- uuid.rawValue as NSString,
- #keyPath(CharacteristicManagedObject.service),
- service)
- fetchRequest.fetchLimit = 1
- fetchRequest.includesSubentities = false
- fetchRequest.returnsObjectsAsFaults = false
-
- return try context.fetch(fetchRequest).first
- }
-
- static func findOrCreate(_ uuid: BluetoothUUID,
- service: ServiceManagedObject,
- in context: NSManagedObjectContext) throws -> CharacteristicManagedObject {
-
- if let existing = try find(uuid, service: service, in: context) {
-
- return existing
-
- } else {
-
- // create a new entity
- let newManagedObject = CharacteristicManagedObject(managedObjectContext: context)
-
- // set identifier
- newManagedObject.uuid = uuid.rawValue
- newManagedObject.service = service
-
- return newManagedObject
- }
- }
-}
diff --git a/BluetoothExplorer/Model/CoreDataDecodable.swift b/BluetoothExplorer/Model/CoreDataDecodable.swift
deleted file mode 100644
index 2fabc0a..0000000
--- a/BluetoothExplorer/Model/CoreDataDecodable.swift
+++ /dev/null
@@ -1,92 +0,0 @@
-//
-// CoreDataDecodable.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/18/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-
-/// Specifies how a type can be decoded from Core Data.
-public protocol CoreDataDecodable {
-
- associatedtype ManagedObject: NSManagedObject
-
- init(managedObject: ManagedObject)
-}
-
-public extension NSManagedObjectContext {
-
- /// Executes a fetch request and returns ```CoreDataDecodable``` types.
- func fetch(_ fetchRequest: NSFetchRequest) throws -> [T] {
-
- assert(fetchRequest.resultType == .managedObjectResultType, "Method only supports fetch requests with NSFetchRequestManagedObjectResultType")
-
- let managedObjects = try self.fetch(fetchRequest)
-
- let decodables = managedObjects.map { T.init(managedObject: $0) }
-
- return decodables
- }
- /*
- @inline(__always)
- func managedObjects(_ decodableType: T.Type,
- predicate: NSPredicate? = nil,
- sortDescriptors: [NSSortDescriptor] = [],
- limit: Int = 0) throws -> [T] {
-
- let results = try self.managedObjects(decodableType.ManagedObject.self,
- predicate: predicate,
- sortDescriptors: sortDescriptors,
- limit: limit)
-
- return T.from(managedObjects: results)
- }*/
-}
-
-public func NSFetchedResultsController
- (_ decodable: T.Type,
- delegate: NSFetchedResultsControllerDelegate? = nil,
- predicate: NSPredicate? = nil,
- sortDescriptors: [NSSortDescriptor] = [],
- sectionNameKeyPath: String? = nil,
- context: NSManagedObjectContext) -> NSFetchedResultsController {
-
- let managedObjectType = T.ManagedObject.self
-
- let entity = context.persistentStoreCoordinator!.managedObjectModel[managedObjectType]!
-
- let fetchRequest = NSFetchRequest(entityName: entity.name!)
-
- fetchRequest.predicate = predicate
-
- fetchRequest.sortDescriptors = sortDescriptors
-
- let fetchedResultsController = CoreData.NSFetchedResultsController.init(fetchRequest: fetchRequest,
- managedObjectContext: context,
- sectionNameKeyPath: sectionNameKeyPath,
- cacheName: nil)
-
- fetchedResultsController.delegate = delegate
-
- return fetchedResultsController
-}
-
-public extension CoreDataDecodable {
-
- static func from (managedObjects: C) -> [Self]
- where C.Iterator.Element == ManagedObject {
-
- return managedObjects.map { self.init(managedObject: $0) }
- }
-}
-
-public extension CoreDataDecodable where Self: Hashable {
-
- static func from(managedObjects: Set) -> Set {
-
- return Set(managedObjects.map({ self.init(managedObject: $0) }))
- }
-}
diff --git a/BluetoothExplorer/Model/CoreDataEncodable.swift b/BluetoothExplorer/Model/CoreDataEncodable.swift
deleted file mode 100644
index a26a9b5..0000000
--- a/BluetoothExplorer/Model/CoreDataEncodable.swift
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-// CoreDataEncodable.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/18/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-
-/// Specifies how a type can be encoded to be stored with Core Data.
-public protocol CoreDataEncodable {
-
- associatedtype ManagedObject: NSManagedObject
-
- func save(_ context: NSManagedObjectContext) throws -> ManagedObject
-}
-
-public extension Collection where Iterator.Element: CoreDataEncodable {
-
- func save(_ context: NSManagedObjectContext) throws -> Set {
-
- var managedObjects = ContiguousArray()
- managedObjects.reserveCapacity(numericCast(self.count))
-
- for element in self {
-
- let managedObject = try element.save(context)
-
- managedObjects.append(managedObject)
- }
-
- return Set(managedObjects)
- }
-}
diff --git a/BluetoothExplorer/Model/CoreDataExtensions.swift b/BluetoothExplorer/Model/CoreDataExtensions.swift
deleted file mode 100644
index 1e6d16d..0000000
--- a/BluetoothExplorer/Model/CoreDataExtensions.swift
+++ /dev/null
@@ -1,152 +0,0 @@
-//
-// CoreDataExtensions.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/15/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-
-internal extension NSManagedObjectContext {
-
- /// Wraps the block to allow for error throwing.
- func performErrorBlockAndWait(_ block: @escaping () throws -> Result) throws -> Result {
-
- var blockError: Swift.Error?
-
- var result: Result!
-
- self.performAndWait {
-
- do { result = try block() }
-
- catch { blockError = error }
-
- return
- }
-
- if let error = blockError {
-
- throw error
- }
-
- return result
- }
-
- func find (identifier: NSObject, property: String, entityName: String) throws -> T? {
-
- let fetchRequest = NSFetchRequest(entityName: entityName)
- fetchRequest.predicate = NSPredicate(format: "%K == %@", property, identifier)
- fetchRequest.fetchLimit = 1
- fetchRequest.includesSubentities = true
- fetchRequest.returnsObjectsAsFaults = false
-
- return try self.fetch(fetchRequest).first
- }
-
- func all (_ managedObjectType: T.Type) throws -> [T] {
-
- let fetchRequest = NSFetchRequest()
- fetchRequest.entity = T.entity(in: self)
- fetchRequest.includesSubentities = false
- fetchRequest.returnsObjectsAsFaults = true
-
- return try self.fetch(fetchRequest) as! [T]
- }
-
- func findOrCreate (identifier: NSObject, property: String, entityName: String) throws -> T {
-
- if let existing: T = try self.find(identifier: identifier, property: property, entityName: entityName) {
-
- return existing
-
- } else {
-
- // create a new entity
- let newManagedObject = NSEntityDescription.insertNewObject(forEntityName: entityName, into: self) as! T
-
- // set resource ID
- newManagedObject.setValue(identifier, forKey: property)
-
- return newManagedObject
- }
- }
-}
-
-internal extension NSManagedObject {
-
- static func entity(in context: NSManagedObjectContext) -> NSEntityDescription {
-
- let className = NSStringFromClass(self as AnyClass)
-
- struct Cache {
- static var entities = [String: NSEntityDescription]()
- }
-
- // try to get from cache
- if let entity = Cache.entities[className] {
-
- return entity
- }
-
- // search for entity with class name
- guard let entity = context.persistentStoreCoordinator?.managedObjectModel[self]
- else { fatalError("Could not find entity for \(type(of: self))") }
-
- Cache.entities[className] = entity
-
- return entity
- }
-
- convenience init(managedObjectContext context: NSManagedObjectContext) {
-
- if #available(iOS 10.0, *) {
-
- self.init(context: context)
-
- } else {
-
- self.init(entity: type(of: self).entity(in: context), insertInto: context)
- }
- }
-}
-
-extension NSManagedObjectModel {
-
- subscript(managedObjectType: NSManagedObject.Type) -> NSEntityDescription? {
-
- // search for entity with class name
-
- let className = NSStringFromClass(managedObjectType)
-
- return self.entities.first { $0.managedObjectClassName == className }
- }
-}
-
-public func NSFetchedResultsController
- (_ managedObjectType: T.Type,
- delegate: NSFetchedResultsControllerDelegate? = nil,
- predicate: NSPredicate? = nil,
- sortDescriptors: [NSSortDescriptor] = [],
- sectionNameKeyPath: String? = nil,
- context: NSManagedObjectContext) -> NSFetchedResultsController {
-
- let entity = context.persistentStoreCoordinator!.managedObjectModel[managedObjectType]!
-
- let fetchRequest = NSFetchRequest(entityName: entity.name!)
-
- fetchRequest.predicate = predicate
-
- fetchRequest.sortDescriptors = sortDescriptors
-
- let fetchedResultsController = CoreData.NSFetchedResultsController.init(fetchRequest: fetchRequest,
- managedObjectContext: context,
- sectionNameKeyPath: sectionNameKeyPath,
- cacheName: nil)
-
- fetchedResultsController.delegate = delegate
-
- return fetchedResultsController
-}
diff --git a/BluetoothExplorer/Model/DeviceStore.swift b/BluetoothExplorer/Model/DeviceStore.swift
deleted file mode 100644
index 56f640f..0000000
--- a/BluetoothExplorer/Model/DeviceStore.swift
+++ /dev/null
@@ -1,541 +0,0 @@
-//
-// DeviceStore.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/15/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-import CoreBluetooth
-import Bluetooth
-import GATT
-
-public final class DeviceStore {
-
- // MARK: - Properties
-
- /// The managed object context used for caching.
- public let managedObjectContext: NSManagedObjectContext
-
- /// The Bluetooth Low Energy GATT Central this `Store` will use for device requests.
- public let centralManager: CentralManager
-
- /// A convenience variable for the managed object model.
- private let managedObjectModel: NSManagedObjectModel
-
- /// Block for creating the persistent store.
- private let createPersistentStore: (NSPersistentStoreCoordinator) throws -> NSPersistentStore
-
- /// Block for resetting the persistent store.
- private let deletePersistentStore: (NSPersistentStoreCoordinator, NSPersistentStore?) throws -> ()
-
- private let persistentStoreCoordinator: NSPersistentStoreCoordinator
-
- private var persistentStore: NSPersistentStore
-
- /// The managed object context running on a background thread for asyncronous caching.
- private let privateQueueManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
-
- private lazy var shouldPatchCoreData: Bool = ProcessInfo().isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 10, minorVersion: 0, patchVersion: 0)) == false
-
- private lazy var centralIdentifier: String = {
-
- return self.centralManager.identifier ?? "org.pureswift.GATT.CentralManager.default"
- }()
-
- // MARK: - Initialization
-
- deinit {
-
- // stop recieving 'didSave' notifications from private context
- NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSManagedObjectContextDidSave, object: self.privateQueueManagedObjectContext)
-
- NotificationCenter.default.removeObserver(self)
- }
-
- public init(contextConcurrencyType: NSManagedObjectContextConcurrencyType = .mainQueueConcurrencyType,
- createPersistentStore: @escaping (NSPersistentStoreCoordinator) throws -> NSPersistentStore,
- deletePersistentStore: @escaping (NSPersistentStoreCoordinator, NSPersistentStore?) throws -> (),
- centralManager: CentralManager) throws {
-
- // store values
- self.createPersistentStore = createPersistentStore
- self.deletePersistentStore = deletePersistentStore
- self.centralManager = centralManager
-
- // set managed object model
- self.managedObjectModel = DeviceStore.managedObjectModel
- self.persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
-
- // setup managed object contexts
- self.managedObjectContext = NSManagedObjectContext(concurrencyType: contextConcurrencyType)
- self.managedObjectContext.undoManager = nil
- self.managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
-
- self.privateQueueManagedObjectContext.undoManager = nil
- self.privateQueueManagedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
- self.privateQueueManagedObjectContext.name = "\(type(of: self)) Private Managed Object Context"
-
- // configure CoreData backing store
- self.persistentStore = try createPersistentStore(persistentStoreCoordinator)
-
- // listen for notifications (for merging changes)
- NotificationCenter.default.addObserver(self,
- selector: #selector(mergeChangesFromContextDidSaveNotification),
- name: .NSManagedObjectContextDidSave,
- object: self.privateQueueManagedObjectContext)
-
- // update cache
- resetPeripherals()
- }
-
- // MARK: Requests
-
- /// The default Central managed object.
- public var central: CentralManagedObject {
-
- let context = privateQueueManagedObjectContext
-
- let centralIdentifier = self.centralIdentifier
-
- do {
-
- let managedObjectID: NSManagedObjectID = try context.performErrorBlockAndWait {
-
- let managedObject = try CentralManagedObject.findOrCreate(centralIdentifier, in: context)
-
- if managedObject.objectID.isTemporaryID {
-
- try context.save()
- }
-
- return managedObject.objectID
- }
-
- assert(managedObjectID.isTemporaryID == false, "Managed object \(managedObjectID) should be persisted")
-
- return managedObjectContext.object(with: managedObjectID) as! CentralManagedObject
- }
-
- catch { fatalError("Could not cache \(error)") }
- }
-
- /// Scans for nearby devices.
- ///
- /// - Parameter duration: The duration of the scan.
- public func scan(duration: TimeInterval, filterDuplicates: Bool = true) throws {
-
- let end = Date() + duration
-
- let centralIdentifier = self.centralIdentifier
-
- var oldPeripherals: [NSManagedObjectID] = updateCache {
-
- let fetchRequest = NSFetchRequest()
- fetchRequest.entity = PeripheralManagedObject.entity(in: $0)
- fetchRequest.predicate = NSPredicate(format: "%K == %@",
- #keyPath(PeripheralManagedObject.isAvailible),
- true as NSNumber)
-
- fetchRequest.includesSubentities = false
- fetchRequest.returnsObjectsAsFaults = true
- fetchRequest.resultType = .managedObjectIDResultType
-
- return try $0.fetch(fetchRequest)
- }
-
- try centralManager.scan(filterDuplicates: filterDuplicates, shouldContinueScanning: { Date() < end }, foundDevice: { [unowned self] (scanData) in
-
- self.updateCache {
-
- let central = try CentralManagedObject.findOrCreate(centralIdentifier, in: $0)
-
- let peripheral = try PeripheralManagedObject.findOrCreate(scanData.peripheral.identifier,
- in: $0)
- peripheral.isAvailible = true
- peripheral.isConnected = false
- peripheral.central = central
- peripheral.scanData.update(scanData)
-
- // remove from old peripherals
- if let index = oldPeripherals.index(of: peripheral.objectID) {
-
- oldPeripherals.remove(at: index)
- }
- }
- })
-
- // update old peripherals
- updateCache { (context) in
-
- oldPeripherals.forEach { context.delete(context.object(with: $0)) }
- }
- }
-
- public func discoverServices(for peripheralManagedObject: PeripheralManagedObject) throws {
-
- // perform BLE operation
- let peripheral = Peripheral(identifier: peripheralManagedObject.attributesView.identifier)
-
- let foundServices = try device(for: peripheral) {
- try centralManager.discoverServices(for: peripheral)
- }
-
- // cache
- let context = privateQueueManagedObjectContext
-
- do {
-
- try context.performErrorBlockAndWait {
-
- let peripheral = context.object(with: peripheralManagedObject.objectID) as! PeripheralManagedObject
-
- // insert new services
- let serviceManagedObjects: [ServiceManagedObject] = try foundServices.map {
- let managedObject = try ServiceManagedObject.findOrCreate($0.uuid, peripheral: peripheral, in: context)
- managedObject.isPrimary = $0.isPrimary
- return managedObject
- }
-
- // remove old services
- peripheral.services
- .filter { serviceManagedObjects.contains($0) == false }
- .forEach { context.delete($0) }
-
- // save
- try context.save()
- }
- }
-
- catch {
- dump(error)
- assertionFailure("Could not cache")
- return
- }
- }
-
- public func discoverCharacteristics(for serviceManagedObject: ServiceManagedObject) throws {
-
- assert(serviceManagedObject.value(forKey: #keyPath(ServiceManagedObject.peripheral)) != nil, "Invalid service")
-
- // perform BLE operation
- let peripheral = Peripheral(identifier: serviceManagedObject.peripheral.attributesView.identifier)
-
- let foundCharacteristics: [Characteristic] = try device(for: peripheral) {
-
- let services = try centralManager.discoverServices(for: peripheral)
-
- guard let foundService = services.first(where: { $0.uuid.rawValue == serviceManagedObject.uuid })
- else { throw CentralError.invalidAttribute(serviceManagedObject.attributesView.uuid) }
-
- return try centralManager.discoverCharacteristics(for: foundService.uuid, peripheral: peripheral)
- }
-
- // cache
- let context = privateQueueManagedObjectContext
-
- do {
-
- try context.performErrorBlockAndWait {
-
- let service = context.object(with: serviceManagedObject.objectID) as! ServiceManagedObject
-
- // insert new characteristics
- let newManagedObjects: [CharacteristicManagedObject] = try foundCharacteristics.map {
- let managedObject = try CharacteristicManagedObject.findOrCreate($0.uuid,
- service: service,
- in: context)
- managedObject.properties = Int16($0.properties.rawValue)
- return managedObject
- }
-
- // remove old characteristics
- service.characteristics
- .filter { newManagedObjects.contains($0) == false }
- .forEach { context.delete($0) }
-
- // save
- try context.save()
- }
- }
-
- catch {
- dump(error)
- assertionFailure("Could not cache")
- return
- }
- }
-
- public func readValue(for characteristicManagedObject: CharacteristicManagedObject) throws {
-
- let serviceManagedObject = characteristicManagedObject.service
-
- assert(serviceManagedObject.value(forKey: #keyPath(ServiceManagedObject.peripheral)) != nil, "Invalid service")
-
- // perform BLE operation
- let peripheral = Peripheral(identifier: characteristicManagedObject.service.peripheral.attributesView.identifier)
-
- let value: Data = try device(for: peripheral) {
-
- let services = try centralManager.discoverServices(for: peripheral)
-
- guard let foundService = services.first(where: { $0.uuid.rawValue == serviceManagedObject.uuid })
- else { throw CentralError.invalidAttribute(serviceManagedObject.attributesView.uuid) }
-
- let characteristics = try centralManager.discoverCharacteristics(for: foundService.uuid,
- peripheral: peripheral)
-
- guard let foundCharacteristic = characteristics.first(where: { $0.uuid.rawValue == characteristicManagedObject.uuid })
- else { throw CentralError.invalidAttribute(characteristicManagedObject.attributesView.uuid) }
-
- return try centralManager.readValue(for: foundCharacteristic.uuid,
- service: foundService.uuid,
- peripheral: peripheral)
- }
-
- updateCache {
-
- let characteristic = $0.object(with: characteristicManagedObject.objectID) as! CharacteristicManagedObject
-
- characteristic.value = value
- }
- }
-
- public func writeValue(_ data: Data, withResponse: Bool = true, for characteristicManagedObject: CharacteristicManagedObject) throws {
-
- let serviceManagedObject = characteristicManagedObject.service
-
- assert(serviceManagedObject.value(forKey: #keyPath(ServiceManagedObject.peripheral)) != nil, "Invalid service")
-
- // perform BLE operation
- let peripheral = Peripheral(identifier: characteristicManagedObject.service.peripheral.attributesView.identifier)
-
- try device(for: peripheral) {
-
- let services = try centralManager.discoverServices(for: peripheral)
-
- guard let foundService = services.first(where: { $0.uuid.rawValue == serviceManagedObject.uuid })
- else { throw CentralError.invalidAttribute(serviceManagedObject.attributesView.uuid) }
-
- let characteristics = try centralManager.discoverCharacteristics(for: foundService.uuid,
- peripheral: peripheral)
-
- guard let foundCharacteristic = characteristics.first(where: { $0.uuid.rawValue == characteristicManagedObject.uuid })
- else { throw CentralError.invalidAttribute(characteristicManagedObject.attributesView.uuid) }
-
- try centralManager.writeValue(data,
- for: foundCharacteristic.uuid,
- withResponse: withResponse,
- service: foundService.uuid,
- peripheral: peripheral)
- }
-
- updateCache {
-
- let characteristic = $0.object(with: characteristicManagedObject.objectID) as! CharacteristicManagedObject
-
- characteristic.value = data
- }
- }
-
- // MARK: - Private Methods
-
- private func resetPeripherals() {
-
- updateCache {
-
- // mark all peripherals as unavailible
- try $0.all(PeripheralManagedObject.self).forEach {
- $0.isAvailible = false
- $0.isConnected = false
- }
- }
- }
-
- private func updateCache (_ update: @escaping (NSManagedObjectContext) throws -> T) -> T {
-
- let context = self.privateQueueManagedObjectContext
-
- do {
-
- return try context.performErrorBlockAndWait {
-
- // fetch, insert, delete or update managed objects
- let result = try update(context)
-
- // save context
- try context.save()
-
- return result
- }
- }
-
- catch {
- dump(error)
- fatalError("Could not save CoreData context \(context) \(error)")
- }
- }
-
- /// Connects to the device, fetches the data, and performs the action, and disconnects.
- private func device (for peripheral: Peripheral, _ action: () throws -> (T)) throws -> T {
-
- // connect first
- try centralManager.connect(to: peripheral)
-
- let managedObject: PeripheralManagedObject = updateCache {
- let managedObject = try PeripheralManagedObject.findOrCreate(peripheral.identifier, in: $0)
- managedObject.isConnected = true
- return managedObject
- }
-
- defer {
- centralManager.disconnect(peripheral: peripheral)
- updateCache { _ in managedObject.isConnected = false }
- }
-
- // perform action
- return try action()
- }
-
- // MARK: Notifications
-
- @objc private func mergeChangesFromContextDidSaveNotification(_ notification: Notification) {
-
- let mainContext = self.managedObjectContext
-
- let shouldPatchCoreData = self.shouldPatchCoreData
-
- guard let savedContext = notification.object as? NSManagedObjectContext
- else { assertionFailure("Invalid notification \(notification)"); return }
-
- // only merge non-main thread context
- guard savedContext !== mainContext
- else { return }
-
- DispatchQueue.main.async {
-
- mainContext.mergeChanges(fromContextDidSave: notification)
-
- // iOS 9 fixes
- // http://openradar.appspot.com/15552115
- if shouldPatchCoreData {
-
- let privateQueueObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set ?? []
-
- // Force the refresh of updated objects which may not have been registered in this context.
- let mainContextObjects = privateQueueObjects.map {
- (try? mainContext.existingObject(with: $0.objectID)) ?? mainContext.object(with: $0.objectID)
- }
-
- mainContextObjects.forEach {
- mainContext.refresh($0, mergeChanges: true)
- $0.willAccessValue(forKey: nil)
- }
- }
- }
- }
-}
-
-// MARK: - Extensions
-
-public extension DeviceStore {
-
- public static var managedObjectModel: NSManagedObjectModel {
-
- guard let fileURL = Bundle(for: self).url(forResource: "Model", withExtension: "momd"),
- let model = NSManagedObjectModel(contentsOf: fileURL)
- else { fatalError("Could not load CoreData model file") }
-
- return model
- }
-}
-
-// MARK: - Singleton
-
-public extension DeviceStore {
-
- /// The default store.
- public static var shared: DeviceStore {
-
- struct Static {
-
- static let store = try! DeviceStore(createPersistentStore: DeviceStore.createPersistentStore,
- deletePersistentStore: DeviceStore.deletePersistentStore,
- centralManager: CentralManager(options: [
- CBCentralManagerOptionRestoreIdentifierKey:
- Bundle.main.bundleIdentifier ?? "org.pureswift.GATT.CentralManager"
- ]))
- }
-
- return Static.store
- }
-
- internal static let fileURL: URL = {
-
- let fileManager = FileManager.default
-
- // get cache folder
-
- let cacheURL = try! fileManager.url(for: .cachesDirectory,
- in: .userDomainMask,
- appropriateFor: nil,
- create: false)
-
-
- // get app folder
- let bundleIdentifier = Bundle.main.bundleIdentifier ?? "org.pureswift.GATT"
- let folderURL = cacheURL.appendingPathComponent(bundleIdentifier, isDirectory: true)
-
- // create folder if doesnt exist
- var folderExists: ObjCBool = false
- if fileManager.fileExists(atPath: folderURL.path, isDirectory: &folderExists) == false
- || folderExists.boolValue == false {
-
- try! fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true)
- }
-
- let fileURL = folderURL.appendingPathComponent("GATT.sqlite", isDirectory: false)
-
- return fileURL
- }()
-
- internal static func createPersistentStore(_ coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore {
-
- func createStore() throws -> NSPersistentStore {
-
- return try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
- configurationName: nil,
- at: DeviceStore.fileURL,
- options: nil)
- }
-
- do { return try createStore() }
-
- catch {
-
- // delete file
- try DeviceStore.deletePersistentStore(coordinator, nil)
-
- // try again
- return try createStore()
- }
- }
-
- internal static func deletePersistentStore(_ coordinator: NSPersistentStoreCoordinator, _ persistentStore: NSPersistentStore? = nil) throws {
-
- let url = self.fileURL
-
- if FileManager.default.fileExists(atPath: url.path) {
-
- // delete file
- try FileManager.default.removeItem(at: url)
- }
-
- if let persistentStore = persistentStore {
-
- try coordinator.remove(persistentStore)
- }
- }
-}
diff --git a/BluetoothExplorer/Model/Model.xcdatamodeld/Model.xcdatamodel/contents b/BluetoothExplorer/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
deleted file mode 100644
index 470188a..0000000
--- a/BluetoothExplorer/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BluetoothExplorer/Model/PeripheralManagedObject.swift b/BluetoothExplorer/Model/PeripheralManagedObject.swift
deleted file mode 100644
index 86c1aca..0000000
--- a/BluetoothExplorer/Model/PeripheralManagedObject.swift
+++ /dev/null
@@ -1,109 +0,0 @@
-//
-// PeripheralManagedObject.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/15/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-
-/// CoreData managed object for a scanned Peripheral.
-public final class PeripheralManagedObject: NSManagedObject {
-
- // MARK: - Attributes
-
- @NSManaged
- public var identifier: String
-
- @NSManaged
- public var isConnected: Bool
-
- @NSManaged
- public var isAvailible: Bool
-
- // MARK: - Properties
-
- @NSManaged
- public var central: CentralManagedObject
-
- @NSManaged
- public var scanData: ScanDataManagedObject
-
- @NSManaged
- public var services: Set
-
- public override func awakeFromInsert() {
- super.awakeFromInsert()
-
- guard let context = self.managedObjectContext
- else { fatalError("Missing \(NSManagedObjectContext.self)") }
-
- self.scanData = ScanDataManagedObject(managedObjectContext: context)
- }
-}
-
-// MARK: - Computed Properties
-
-public extension PeripheralManagedObject {
-
- public struct AttributesView {
-
- public var identifier: UUID
-
- public var isConnected: Bool
- }
-
- public var attributesView: AttributesView {
-
- guard let identifier = UUID(rawValue: self.identifier)
- else { fatalError("Invalid stored value \(self.identifier)") }
-
- return AttributesView(identifier: identifier, isConnected: self.isConnected)
- }
-}
-
-// MARK: - Fetch Requests
-
-extension PeripheralManagedObject {
-
- static func find(_ identifier: UUID,
- in context: NSManagedObjectContext) throws -> PeripheralManagedObject? {
-
- let identifier = identifier.uuidString as NSString
-
- let identifierProperty = #keyPath(PeripheralManagedObject.identifier)
-
- let entityName = self.entity(in: context).name!
-
- return try context.find(identifier: identifier, property: identifierProperty, entityName: entityName)
- }
-
- static func findOrCreate(_ identifier: UUID,
- in context: NSManagedObjectContext) throws -> PeripheralManagedObject {
-
- let identifier = identifier.uuidString as NSString
-
- let identifierProperty = #keyPath(PeripheralManagedObject.identifier)
-
- let entityName = self.entity(in: context).name!
-
- return try context.findOrCreate(identifier: identifier, property: identifierProperty, entityName: entityName)
- }
-}
-
-// MARK: - CoreData Decodable
-/*
-public extension PeripheralModel {
-
- init(managedObject: PeripheralManagedObject) {
-
- self.identifier = UUID(uuidString: managedObject.identifier)!
- self.isConnected = managedObject.isConnected
- self.central = managedObject.central.identifier
- self.scanData = ScanData()
- self.services = services
- }
-}
-*/
diff --git a/BluetoothExplorer/Model/PeripheralModel.swift b/BluetoothExplorer/Model/PeripheralModel.swift
deleted file mode 100644
index 4ee9859..0000000
--- a/BluetoothExplorer/Model/PeripheralModel.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// PeripheralModel.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/15/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import GATT
-
-public struct PeripheralModel {
-
- // MARK: - Attributes
-
- public let identifier: UUID
-
- public var isConnected: Bool
-
- // MARK: - Relationships
-
- public let central: String
-
- public var scanData: ScanData
-
- public var services: GATT.Service
-}
diff --git a/BluetoothExplorer/Model/ScanDataManagedObject.swift b/BluetoothExplorer/Model/ScanDataManagedObject.swift
deleted file mode 100644
index 8500284..0000000
--- a/BluetoothExplorer/Model/ScanDataManagedObject.swift
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-// ScanEventManagedObject.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/15/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-import Bluetooth
-import GATT
-
-/// CoreData managed object for a scan event.
-public final class ScanDataManagedObject: NSManagedObject {
-
- // MARK: - Attributes
-
- @NSManaged
- public var date: Date
-
- @NSManaged
- public var rssi: Double
-
- // MARK: - Relationships
-
- @NSManaged
- public var peripheral: PeripheralManagedObject
-
- @NSManaged
- public var advertisementData: AdvertisementDataManagedObject
-
- public override func awakeFromInsert() {
- super.awakeFromInsert()
-
- guard let context = self.managedObjectContext
- else { fatalError("Missing NSManagedObjectContext") }
-
- self.date = Date()
- self.advertisementData = AdvertisementDataManagedObject(managedObjectContext: context)
- }
-}
-
-// MARK: - CoreData Encodable
-
-public extension ScanDataManagedObject {
-
- func update(_ value: ScanData) {
-
- self.date = value.date
- self.rssi = value.rssi
- self.advertisementData.update(value.advertisementData)
- }
-}
-
-// MARK: - CoreData Decodable
-
-extension ScanData: CoreDataDecodable {
-
- public init(managedObject: ScanDataManagedObject) {
-
- guard let uuid = UUID(uuidString: managedObject.peripheral.identifier)
- else { fatalError("Invalid stored value") }
-
- self.date = managedObject.date
- self.rssi = managedObject.rssi
- self.peripheral = Peripheral(identifier: uuid)
- self.advertisementData = AdvertisementData(managedObject: managedObject.advertisementData)
- }
-}
diff --git a/BluetoothExplorer/Model/ServiceManagedObject.swift b/BluetoothExplorer/Model/ServiceManagedObject.swift
deleted file mode 100644
index cacb0e2..0000000
--- a/BluetoothExplorer/Model/ServiceManagedObject.swift
+++ /dev/null
@@ -1,119 +0,0 @@
-//
-// ServiceManagedObject.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/18/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import CoreData
-import Bluetooth
-import GATT
-
-/// CoreData managed object for discovered GATT Service.
-public final class ServiceManagedObject: NSManagedObject {
-
- @NSManaged
- public var uuid: String
-
- @NSManaged
- public var isPrimary: Bool
-
- // MARK: - Relationships
-
- @NSManaged
- public var peripheral: PeripheralManagedObject
-
- @NSManaged
- public var characteristics: Set
-}
-
-// MARK: - Computed Properties
-
-public extension ServiceManagedObject {
-
- public struct AttributesView {
-
- public var uuid: BluetoothUUID
-
- public var isPrimary: Bool
- }
-
- public var attributesView: AttributesView {
-
- guard let uuid = BluetoothUUID(rawValue: self.uuid)
- else { fatalError("Invalid stored value \(self.uuid)") }
-
- return AttributesView(uuid: uuid, isPrimary: self.isPrimary)
- }
-}
-
-// MARK: - CoreData Encodable
-
-public extension ServiceManagedObject {
-
- func update(_ value: Service) {
-
- self.uuid = value.uuid.rawValue
- self.isPrimary = value.isPrimary
- }
-}
-
-// MARK: - CoreData Decodable
-
-extension Service: CoreDataDecodable {
-
- public init(managedObject: ServiceManagedObject) {
-
- guard let uuid = BluetoothUUID(rawValue: managedObject.uuid)
- else { fatalError("Invalid value \(#keyPath(ServiceManagedObject.uuid)) \(managedObject.uuid)") }
-
- self.uuid = uuid
- self.isPrimary = managedObject.isPrimary
- }
-}
-
-// MARK: - Fetch Requests
-
-extension ServiceManagedObject {
-
- static func find(_ uuid: BluetoothUUID,
- peripheral: PeripheralManagedObject,
- in context: NSManagedObjectContext) throws -> ServiceManagedObject? {
-
- let entityName = self.entity(in: context).name!
- let fetchRequest = NSFetchRequest(entityName: entityName)
- fetchRequest.predicate = NSPredicate(format: "%K == %@ && %K == %@",
- #keyPath(ServiceManagedObject.uuid),
- uuid.rawValue as NSString,
- #keyPath(ServiceManagedObject.peripheral),
- peripheral)
- fetchRequest.fetchLimit = 1
- fetchRequest.includesSubentities = false
- fetchRequest.returnsObjectsAsFaults = false
-
- return try context.fetch(fetchRequest).first
- }
-
- static func findOrCreate(_ uuid: BluetoothUUID,
- peripheral: PeripheralManagedObject,
- in context: NSManagedObjectContext) throws -> ServiceManagedObject {
-
- if let existing = try find(uuid, peripheral: peripheral, in: context) {
-
- return existing
-
- } else {
-
- // create a new entity
- let newManagedObject = ServiceManagedObject(managedObjectContext: context)
-
- // set identifier
- newManagedObject.uuid = uuid.rawValue
- newManagedObject.peripheral = peripheral
-
- return newManagedObject
- }
- }
-}
diff --git a/BluetoothExplorer/ModelNumberCharacteristic.storyboard b/BluetoothExplorer/ModelNumberCharacteristic.storyboard
deleted file mode 100644
index b7afbd6..0000000
--- a/BluetoothExplorer/ModelNumberCharacteristic.storyboard
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/PnPIDCharacteristic.storyboard b/BluetoothExplorer/PnPIDCharacteristic.storyboard
deleted file mode 100644
index 52d9b8e..0000000
--- a/BluetoothExplorer/PnPIDCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/SerialNumberStringCharacteristic.storyboard b/BluetoothExplorer/SerialNumberStringCharacteristic.storyboard
deleted file mode 100644
index b6aa566..0000000
--- a/BluetoothExplorer/SerialNumberStringCharacteristic.storyboard
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/SoftwareRevisionStringCharacteristic.storyboard b/BluetoothExplorer/SoftwareRevisionStringCharacteristic.storyboard
deleted file mode 100644
index ac96471..0000000
--- a/BluetoothExplorer/SoftwareRevisionStringCharacteristic.storyboard
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/SystemIDCharacteristic.storyboard b/BluetoothExplorer/SystemIDCharacteristic.storyboard
deleted file mode 100644
index 3d2042e..0000000
--- a/BluetoothExplorer/SystemIDCharacteristic.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/View/Appearance.swift b/BluetoothExplorer/View/Appearance.swift
deleted file mode 100644
index 67a0d9e..0000000
--- a/BluetoothExplorer/View/Appearance.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// Appearance.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/22/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-
-internal func ConfigureAppearance() {
-
- UINavigationBar.appearance().tintColor = .white
-
- if #available(iOS 11.0, *) {
- UINavigationBar.appearance().prefersLargeTitles = true
- UINavigationBar.appearance().barTintColor = UIColor(named: "NavigationBarTintColor")!
- UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.white]
- } else {
- UINavigationBar.appearance().barTintColor = UIColor(red: 0.386, green: 0.707, blue: 1.000, alpha: 1.0)
- }
-}
diff --git a/BluetoothExplorer/View/CustomButton.swift b/BluetoothExplorer/View/CustomButton.swift
deleted file mode 100644
index e9b5e81..0000000
--- a/BluetoothExplorer/View/CustomButton.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// Button.swift
-// BluetoothExplorer
-//
-// Created by Alsey Coleman Miller on 6/20/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-
-///
-@IBDesignable final class CustomButton: UIButton {
-
- // MARK: - Properties
-
- @IBInspectable
- public var cornerRadius: CGFloat {
-
- get { return layer.cornerRadius }
-
- set { layer.cornerRadius = newValue }
- }
-
- @IBInspectable
- public var borderWidth: CGFloat {
-
- get { return layer.borderWidth }
-
- set { layer.borderWidth = newValue }
- }
-
- @IBInspectable
- public var borderColor: UIColor {
-
- get { return UIColor(cgColor: layer.borderColor ?? UIColor.clear.cgColor) }
-
- set { layer.borderColor = newValue.cgColor }
- }
-
- // MARK: - Loading
-
- override open func awakeFromNib() {
- super.awakeFromNib()
-
-
- }
-}
diff --git a/BluetoothExplorer/View/InputTextField.swift b/BluetoothExplorer/View/InputTextField.swift
deleted file mode 100644
index 22441e8..0000000
--- a/BluetoothExplorer/View/InputTextField.swift
+++ /dev/null
@@ -1,95 +0,0 @@
-//
-// InputTextField.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/27/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-
-class InputTextField: UITextField {
-
- // MARK: - Properties
-
- var posibleValues = [String]() {
- didSet {
- inputView = (posibleValues.count == 0) ? nil : pickerView
- }
- }
-
- private lazy var pickerView: UIPickerView = {
- let picker = UIPickerView()
- picker.delegate = self
- return picker
- }()
-
- // MARK: - Initializers
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- addToolbar()
- }
-
- required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- addToolbar()
- }
-
- // MARK: - Overrides
-
- override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
- return posibleValues.count == 0
- }
-
- // MARK: - Methods
-
- private func addToolbar() {
-
- let toolbar = UIToolbar()
- toolbar.isTranslucent = true
- toolbar.items = [
- UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
- UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneAction))
- ]
-
- toolbar.sizeToFit()
-
- inputAccessoryView = toolbar
- }
-
- @objc func doneAction() {
-
- self.resignFirstResponder()
- }
-
-}
-
-extension InputTextField: UIPickerViewDataSource {
-
- func numberOfComponents(in pickerView: UIPickerView) -> Int {
-
- return 1
- }
-
- func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
-
- return posibleValues.count
- }
-
-}
-
-extension InputTextField: UIPickerViewDelegate {
-
- func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
-
- return posibleValues[row]
- }
-
- func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
-
- text = posibleValues[row]
- }
-
-}
diff --git a/BluetoothExplorer/View/InputTextView.swift b/BluetoothExplorer/View/InputTextView.swift
deleted file mode 100644
index 66f9e77..0000000
--- a/BluetoothExplorer/View/InputTextView.swift
+++ /dev/null
@@ -1,143 +0,0 @@
-//
-// InputTextView.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/27/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-
-@IBDesignable
-class InputTextView: NibDesignableView {
-
- typealias TextFieldEventClosure = (() -> Void)?
- typealias ValidationClosure = ((String) -> Validation)?
-
- // MARK: - Properties
-
- @IBOutlet weak var fieldLabel: UILabel!
- @IBOutlet weak var textField: InputTextField!
- @IBOutlet weak var messageLabel: UILabel!
-
- @IBInspectable
- public var placeholder: String? {
-
- get { return textField.placeholder }
- set { textField.placeholder = newValue }
- }
-
- @IBInspectable
- public var fieldLabelText: String? {
-
- get { return fieldLabel.text }
- set { fieldLabel.text = newValue }
- }
-
- @IBInspectable
- public var value: String? {
-
- get { return textField.text }
- set { textField.text = newValue }
- }
-
- public var keyboardType: UIKeyboardType {
-
- get { return textField.keyboardType }
- set { textField.keyboardType = newValue }
- }
-
- public var isEnabled: Bool {
-
- get { return textField.isEnabled }
- set { textField.isEnabled = newValue }
- }
-
- public var posibleInputValues: [String] {
-
- get { return textField.posibleValues }
- set { textField.posibleValues = newValue; }
- }
-
- private var validation: Validation = .none {
- didSet { updateView() }
- }
-
- var validator: ValidationClosure
-
- var onBeginEditing: TextFieldEventClosure
- var onEndEditing: TextFieldEventClosure
- var onPressReturn: TextFieldEventClosure
- var onChange: TextFieldEventClosure
-
- public enum Validation {
- case none
- case error(String?)
- }
-
- // MARK: - Initializers
-
- override public init(frame: CGRect) {
- super.init(frame: frame)
- self.setup()
- }
-
- required public init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- self.setup()
- }
-
- // MARK: - Methods
-
- private func setup() {
-
- textField.delegate = self
- textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
- }
-
- private func updateView() {
-
- switch validation {
- case .error(let message):
- messageLabel.text = message
- messageLabel.textColor = .red
-
- case .none:
- messageLabel.text = ""
- }
- }
-
- private func updateValidation() {
-
- validation = validator?(textField.text ?? "") ?? .none
- }
-
-}
-
-extension InputTextView: UITextFieldDelegate {
-
- func textFieldDidBeginEditing(_ textField: UITextField) {
-
- onBeginEditing?()
- updateValidation()
- }
-
- func textFieldDidEndEditing(_ textField: UITextField) {
-
- onEndEditing?()
- updateValidation()
- }
-
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-
- onPressReturn?()
- updateValidation()
- return false
- }
-
- @objc open func textFieldDidChange(_ textField: UITextField) {
-
- onChange?()
- updateValidation()
- }
-}
diff --git a/BluetoothExplorer/View/InputTextView.xib b/BluetoothExplorer/View/InputTextView.xib
deleted file mode 100644
index e43aa74..0000000
--- a/BluetoothExplorer/View/InputTextView.xib
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/View/InputTextViewCell.swift b/BluetoothExplorer/View/InputTextViewCell.swift
deleted file mode 100644
index 8b677c6..0000000
--- a/BluetoothExplorer/View/InputTextViewCell.swift
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// InputTextViewCell.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/27/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import Foundation
-import UIKit
-
-class InputTextViewCell: UITableViewCell {
-
- @IBOutlet weak var inputTextView: InputTextView!
-
-}
diff --git a/BluetoothExplorer/View/InputTextViewCell.xib b/BluetoothExplorer/View/InputTextViewCell.xib
deleted file mode 100644
index 14a9e86..0000000
--- a/BluetoothExplorer/View/InputTextViewCell.xib
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BluetoothExplorer/View/NibDesignable.swift b/BluetoothExplorer/View/NibDesignable.swift
deleted file mode 100644
index 2977377..0000000
--- a/BluetoothExplorer/View/NibDesignable.swift
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// NibDesignable.swift
-// BluetoothExplorer
-//
-// Created by Carlos Duclos on 6/27/18.
-// Copyright © 2018 PureSwift. All rights reserved.
-//
-
-import UIKit
-
-public protocol NibDesignableProtocol: NSObjectProtocol {
-
- var nibContainerView: UIView { get }
-
- func loadNib() -> UIView
-
- func nibName() -> String
-}
-
-extension NibDesignableProtocol {
-
- public func loadNib() -> UIView {
- let bundle = Bundle(for: type(of: self))
- let nib = UINib(nibName: self.nibName(), bundle: bundle)
- return nib.instantiate(withOwner: self, options: nil)[0] as! UIView // swiftlint:disable:this force_cast
- }
-
- fileprivate func setupNib() {
- let view = self.loadNib()
- self.nibContainerView.addSubview(view)
- view.translatesAutoresizingMaskIntoConstraints = false
- let bindings = ["view": view]
- self.nibContainerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|", options:[], metrics:nil, views: bindings))
- self.nibContainerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|", options:[], metrics:nil, views: bindings))
- }
-}
-
-@objc
-extension UIView {
-
- public var nibContainerView: UIView {
- return self
- }
-
- open func nibName() -> String {
- return type(of: self).description().components(separatedBy: ".").last!
- }
-}
-
-@IBDesignable
-open class NibDesignableView: UIView, NibDesignableProtocol {
-
- override public init(frame: CGRect) {
- super.init(frame: frame)
- self.setupNib()
- }
-
- required public init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- self.setupNib()
- }
-}
diff --git a/BluetoothExplorer/en.lproj/Localizable.strings b/BluetoothExplorer/en.lproj/Localizable.strings
deleted file mode 100644
index daa639c..0000000
--- a/BluetoothExplorer/en.lproj/Localizable.strings
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- Localizable.strings
- BluetoothExplorer
-
- Created by Carlos Duclos on 6/28/18.
- Copyright © 2018 PureSwift. All rights reserved.
-*/
-
-"gattAlertCategorySimpleAlert" = "Simple Alert";
-"gattAlertCategoryEmail" = "Email";
-"gattAlertCategoryNews" = "News";
-"gattAlertCategoryCall" = "Call";
-"gattAlertCategoryMissedCall" = "Missed Call";
-"gattAlertCategorySMS" = "SMS";
-"gattAlertCategoryVoiceMail" = "Voice Mail";
-"gattAlertCategorySchedule" = "Schedule";
-"gattAlertCategoryHighPrioritized" = "High Prioritized Alert";
-"gattAlertCategoryInstantMessage" = "Instant Message";
-
-"gattAlertNotificationControlPointDisableNewIncoming" = "Disable new incoming alert notification";
-"gattAlertNotificationControlPointDisableUnreadCategoryStatus" = "Disable unread category status";
-"gattAlertNotificationControlPointEnableNewIncomingAlert" = "Enable new incoming alert notification";
-"gattAlertNotificationControlPointEnableUnreadCategoryStatus" = "Enable unread category status";
-"gattAlertNotificationControlPointNotifyNewIncomingAlert" = "Notify new incoming alert immediately";
-"gattAlertNotificationControlPointNotifyUnreadCategoryStatus" = "Notify unread category status immediately";
-
diff --git a/Cartfile b/Cartfile
index 071a31f..522eae6 100644
--- a/Cartfile
+++ b/Cartfile
@@ -1,4 +1 @@
-github "PureSwift/Bluetooth" "master"
-github "PureSwift/GATT" "master"
-github "MillerTechnologyPeru/FDStackView" "master"
-github "mac-cain13/R.swift.Library"
+github "PureSwift/GATT" "master"
\ No newline at end of file
diff --git a/Cartfile.resolved b/Cartfile.resolved
index 2121f9d..359a6b2 100644
--- a/Cartfile.resolved
+++ b/Cartfile.resolved
@@ -1,4 +1,2 @@
-github "MillerTechnologyPeru/FDStackView" "d507a47d185de1cb0cdd35cdeb28fcdd827d9859"
-github "PureSwift/Bluetooth" "d1fca2499c977cb041c07093fe39b0f13661fa61"
-github "PureSwift/GATT" "fdd3108d134a193dcd24d690afa3567ad05a8030"
-github "mac-cain13/R.swift.Library" "v4.0.0"
+github "PureSwift/Bluetooth" "584a9153ee90fe8b1e92e94e6d009a4bafc8e59a"
+github "PureSwift/GATT" "2841502875c91ba0fb3314951feed12054d70ac5"
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index d67c55a..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2018 Miller Technology
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
deleted file mode 100644
index 555dade..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# BluetoothExplorer
-Bluetooth Explorer iOS App
diff --git a/iOS/BluetoothExplorer.xcodeproj/project.pbxproj b/iOS/BluetoothExplorer.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..397646a
--- /dev/null
+++ b/iOS/BluetoothExplorer.xcodeproj/project.pbxproj
@@ -0,0 +1,756 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 51B6F9B3215567E800251524 /* CharacteristicsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B6F9A7215567E800251524 /* CharacteristicsViewController.swift */; };
+ 6E0596AD2158448B00BB43DC /* CharacteristicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0596A12158448B00BB43DC /* CharacteristicViewController.swift */; };
+ 6E0596EC2158A88500BB43DC /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0596EB2158A88500BB43DC /* Appearance.swift */; };
+ 6E303E072142398F0034A6C1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6E303E062142398F0034A6C1 /* Assets.xcassets */; };
+ 6E303E0A2142398F0034A6C1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E303E082142398F0034A6C1 /* LaunchScreen.storyboard */; };
+ 6E303E1921423AED0034A6C1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E303E1421423AED0034A6C1 /* AppDelegate.swift */; };
+ 6E303E1A21423AED0034A6C1 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E303E1521423AED0034A6C1 /* main.swift */; };
+ 6E733FE121423DFE001B8682 /* GATT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E733FD221423DEC001B8682 /* GATT.framework */; };
+ 6E733FE221423DFE001B8682 /* GATT.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E733FD221423DEC001B8682 /* GATT.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 6E733FE521423DFE001B8682 /* DarwinGATT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E733FDC21423DEC001B8682 /* DarwinGATT.framework */; };
+ 6E733FE621423DFE001B8682 /* DarwinGATT.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E733FDC21423DEC001B8682 /* DarwinGATT.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 6E733FE921423DFE001B8682 /* Bluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E733F9E21423DE0001B8682 /* Bluetooth.framework */; };
+ 6E733FEA21423DFE001B8682 /* Bluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E733F9E21423DE0001B8682 /* Bluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 6E73401F21424322001B8682 /* CentralViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E73401E21424322001B8682 /* CentralViewController.swift */; };
+ 6E73402C21424437001B8682 /* Central.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E73402B21424437001B8682 /* Central.swift */; };
+ 6EAEEE3C2142470C009A7A9F /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAEEE302142470C009A7A9F /* ErrorAlert.swift */; };
+ 6EAEEE3E21424717009A7A9F /* ActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAEEE3D21424717009A7A9F /* ActivityIndicatorViewController.swift */; };
+ 6EAEEE4021424790009A7A9F /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAEEE3F21424790009A7A9F /* Async.swift */; };
+ 6EAEEE42214247F3009A7A9F /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAEEE41214247F3009A7A9F /* Log.swift */; };
+ 6EAEEED221437F2B009A7A9F /* ServicesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAEEED121437F2B009A7A9F /* ServicesViewController.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 6E733F9B21423DE0001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EE84DB71CAF5C7C00A40C4D;
+ remoteInfo = "Bluetooth-macOS";
+ };
+ 6E733F9D21423DE0001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EF45FBD1CC6D04D001F7A39;
+ remoteInfo = "Bluetooth-iOS";
+ };
+ 6E733F9F21423DE0001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EB2EA1D1CD5A8A7000CF975;
+ remoteInfo = "Bluetooth-watchOS";
+ };
+ 6E733FA121423DE0001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6E49B26520532D45002EA5DC;
+ remoteInfo = "Bluetooth-tvOS";
+ };
+ 6E733FA321423DE0001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EE84DC11CAF5C7C00A40C4D;
+ remoteInfo = BluetoothTests;
+ };
+ 6E733FCF21423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EE84D841CAF419D00A40C4D;
+ remoteInfo = "GATT-macOS";
+ };
+ 6E733FD121423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EF45FCF1CC6D355001F7A39;
+ remoteInfo = "GATT-iOS";
+ };
+ 6E733FD321423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EE9103A1FDE5C17007AD3EA;
+ remoteInfo = "GATT-watchOS";
+ };
+ 6E733FD521423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6E49B23A20532A94002EA5DC;
+ remoteInfo = "GATT-tvOS";
+ };
+ 6E733FD721423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6E0FC45A20F873B1009269B4;
+ remoteInfo = GATTTests;
+ };
+ 6E733FD921423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EA5D1562107DE66009998FD;
+ remoteInfo = "DarwinGATT-macOS";
+ };
+ 6E733FDB21423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EA5D16D2107DE6A009998FD;
+ remoteInfo = "DarwinGATT-iOS";
+ };
+ 6E733FDD21423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EA5D1842107DE6D009998FD;
+ remoteInfo = "DarwinGATT-watchOS";
+ };
+ 6E733FDF21423DEC001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 6EA5D19B2107DE70009998FD;
+ remoteInfo = "DarwinGATT-tvOS";
+ };
+ 6E733FE321423DFE001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = 6EF45FBF1CC6D355001F7A39;
+ remoteInfo = "GATT-iOS";
+ };
+ 6E733FE721423DFE001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = 6EA5D1582107DE6A009998FD;
+ remoteInfo = "DarwinGATT-iOS";
+ };
+ 6E733FEB21423DFE001B8682 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = 6EF45FA21CC6D04D001F7A39;
+ remoteInfo = "Bluetooth-iOS";
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 6E733FED21423DFE001B8682 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 6E733FEA21423DFE001B8682 /* Bluetooth.framework in Embed Frameworks */,
+ 6E733FE221423DFE001B8682 /* GATT.framework in Embed Frameworks */,
+ 6E733FE621423DFE001B8682 /* DarwinGATT.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 51B6F9A7215567E800251524 /* CharacteristicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacteristicsViewController.swift; sourceTree = ""; };
+ 6E0596A12158448B00BB43DC /* CharacteristicViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacteristicViewController.swift; sourceTree = ""; };
+ 6E0596EB2158A88500BB43DC /* Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = ""; };
+ 6E303DFC2142398E0034A6C1 /* BluetoothExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BluetoothExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6E303E062142398F0034A6C1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 6E303E092142398F0034A6C1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 6E303E0B2142398F0034A6C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 6E303E1421423AED0034A6C1 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 6E303E1521423AED0034A6C1 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Bluetooth.xcodeproj; path = ../Carthage/Checkouts/Bluetooth/Xcode/Bluetooth.xcodeproj; sourceTree = ""; };
+ 6E733FC321423DEC001B8682 /* GATT.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GATT.xcodeproj; path = ../Carthage/Checkouts/GATT/Xcode/GATT.xcodeproj; sourceTree = ""; };
+ 6E73401E21424322001B8682 /* CentralViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CentralViewController.swift; sourceTree = ""; };
+ 6E73402B21424437001B8682 /* Central.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Central.swift; sourceTree = ""; };
+ 6EAEEE302142470C009A7A9F /* ErrorAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; };
+ 6EAEEE3D21424717009A7A9F /* ActivityIndicatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorViewController.swift; sourceTree = ""; };
+ 6EAEEE3F21424790009A7A9F /* Async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Async.swift; sourceTree = ""; };
+ 6EAEEE41214247F3009A7A9F /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; };
+ 6EAEEED121437F2B009A7A9F /* ServicesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesViewController.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 6E303DF92142398E0034A6C1 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6E733FE921423DFE001B8682 /* Bluetooth.framework in Frameworks */,
+ 6E733FE121423DFE001B8682 /* GATT.framework in Frameworks */,
+ 6E733FE521423DFE001B8682 /* DarwinGATT.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 6E0596EA2158A87600BB43DC /* View */ = {
+ isa = PBXGroup;
+ children = (
+ 6E0596EB2158A88500BB43DC /* Appearance.swift */,
+ );
+ name = View;
+ sourceTree = "";
+ };
+ 6E303DF32142398E0034A6C1 = {
+ isa = PBXGroup;
+ children = (
+ 6E733F9221423DD8001B8682 /* Dependencies */,
+ 6E303E1121423AED0034A6C1 /* Sources */,
+ 6E303DFE2142398E0034A6C1 /* BluetoothExplorer */,
+ 6E303DFD2142398E0034A6C1 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 6E303DFD2142398E0034A6C1 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 6E303DFC2142398E0034A6C1 /* BluetoothExplorer.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 6E303DFE2142398E0034A6C1 /* BluetoothExplorer */ = {
+ isa = PBXGroup;
+ children = (
+ 6E303E062142398F0034A6C1 /* Assets.xcassets */,
+ 6E303E082142398F0034A6C1 /* LaunchScreen.storyboard */,
+ 6E303E0B2142398F0034A6C1 /* Info.plist */,
+ );
+ path = BluetoothExplorer;
+ sourceTree = "";
+ };
+ 6E303E1121423AED0034A6C1 /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ 6E303E1521423AED0034A6C1 /* main.swift */,
+ 6E303E1421423AED0034A6C1 /* AppDelegate.swift */,
+ 6EAEEE3F21424790009A7A9F /* Async.swift */,
+ 6EAEEE41214247F3009A7A9F /* Log.swift */,
+ 6E0596EA2158A87600BB43DC /* View */,
+ 6E73402D21424456001B8682 /* Controller */,
+ 6E73402E21424460001B8682 /* Model */,
+ );
+ name = Sources;
+ path = ../Android/app/src/main/swift/Sources;
+ sourceTree = "";
+ };
+ 6E733F9221423DD8001B8682 /* Dependencies */ = {
+ isa = PBXGroup;
+ children = (
+ 6E733FC321423DEC001B8682 /* GATT.xcodeproj */,
+ 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */,
+ );
+ name = Dependencies;
+ sourceTree = "";
+ };
+ 6E733F9421423DE0001B8682 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 6E733F9C21423DE0001B8682 /* Bluetooth.framework */,
+ 6E733F9E21423DE0001B8682 /* Bluetooth.framework */,
+ 6E733FA021423DE0001B8682 /* Bluetooth.framework */,
+ 6E733FA221423DE0001B8682 /* Bluetooth.framework */,
+ 6E733FA421423DE0001B8682 /* BluetoothTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 6E733FC421423DEC001B8682 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 6E733FD021423DEC001B8682 /* GATT.framework */,
+ 6E733FD221423DEC001B8682 /* GATT.framework */,
+ 6E733FD421423DEC001B8682 /* GATT.framework */,
+ 6E733FD621423DEC001B8682 /* GATT.framework */,
+ 6E733FD821423DEC001B8682 /* GATTTests.xctest */,
+ 6E733FDA21423DEC001B8682 /* DarwinGATT.framework */,
+ 6E733FDC21423DEC001B8682 /* DarwinGATT.framework */,
+ 6E733FDE21423DEC001B8682 /* DarwinGATT.framework */,
+ 6E733FE021423DEC001B8682 /* DarwinGATT.framework */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 6E73402D21424456001B8682 /* Controller */ = {
+ isa = PBXGroup;
+ children = (
+ 6EAEEE4421424869009A7A9F /* Protocols */,
+ 6EAEEE432142485F009A7A9F /* Extensions */,
+ 6E73401E21424322001B8682 /* CentralViewController.swift */,
+ 6EAEEED121437F2B009A7A9F /* ServicesViewController.swift */,
+ 51B6F9A7215567E800251524 /* CharacteristicsViewController.swift */,
+ 6E0596A12158448B00BB43DC /* CharacteristicViewController.swift */,
+ );
+ name = Controller;
+ sourceTree = "";
+ };
+ 6E73402E21424460001B8682 /* Model */ = {
+ isa = PBXGroup;
+ children = (
+ 6E73402B21424437001B8682 /* Central.swift */,
+ );
+ name = Model;
+ sourceTree = "";
+ };
+ 6EAEEE432142485F009A7A9F /* Extensions */ = {
+ isa = PBXGroup;
+ children = (
+ 6EAEEE302142470C009A7A9F /* ErrorAlert.swift */,
+ );
+ name = Extensions;
+ sourceTree = "";
+ };
+ 6EAEEE4421424869009A7A9F /* Protocols */ = {
+ isa = PBXGroup;
+ children = (
+ 6EAEEE3D21424717009A7A9F /* ActivityIndicatorViewController.swift */,
+ );
+ name = Protocols;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 6E303DFB2142398E0034A6C1 /* BluetoothExplorer */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6E303E0E2142398F0034A6C1 /* Build configuration list for PBXNativeTarget "BluetoothExplorer" */;
+ buildPhases = (
+ 6E303DF82142398E0034A6C1 /* Sources */,
+ 6E303DF92142398E0034A6C1 /* Frameworks */,
+ 6E303DFA2142398E0034A6C1 /* Resources */,
+ 6E733FED21423DFE001B8682 /* Embed Frameworks */,
+ 6E0596E92158A46500BB43DC /* Increment Build */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 6E733FEC21423DFE001B8682 /* PBXTargetDependency */,
+ 6E733FE421423DFE001B8682 /* PBXTargetDependency */,
+ 6E733FE821423DFE001B8682 /* PBXTargetDependency */,
+ );
+ name = BluetoothExplorer;
+ productName = BluetoothExplorer;
+ productReference = 6E303DFC2142398E0034A6C1 /* BluetoothExplorer.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 6E303DF42142398E0034A6C1 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0940;
+ LastUpgradeCheck = 0940;
+ ORGANIZATIONNAME = PureSwift;
+ TargetAttributes = {
+ 6E303DFB2142398E0034A6C1 = {
+ CreatedOnToolsVersion = 9.4.1;
+ };
+ };
+ };
+ buildConfigurationList = 6E303DF72142398E0034A6C1 /* Build configuration list for PBXProject "BluetoothExplorer" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 6E303DF32142398E0034A6C1;
+ productRefGroup = 6E303DFD2142398E0034A6C1 /* Products */;
+ projectDirPath = "";
+ projectReferences = (
+ {
+ ProductGroup = 6E733F9421423DE0001B8682 /* Products */;
+ ProjectRef = 6E733F9321423DE0001B8682 /* Bluetooth.xcodeproj */;
+ },
+ {
+ ProductGroup = 6E733FC421423DEC001B8682 /* Products */;
+ ProjectRef = 6E733FC321423DEC001B8682 /* GATT.xcodeproj */;
+ },
+ );
+ projectRoot = "";
+ targets = (
+ 6E303DFB2142398E0034A6C1 /* BluetoothExplorer */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXReferenceProxy section */
+ 6E733F9C21423DE0001B8682 /* Bluetooth.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = Bluetooth.framework;
+ remoteRef = 6E733F9B21423DE0001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733F9E21423DE0001B8682 /* Bluetooth.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = Bluetooth.framework;
+ remoteRef = 6E733F9D21423DE0001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FA021423DE0001B8682 /* Bluetooth.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = Bluetooth.framework;
+ remoteRef = 6E733F9F21423DE0001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FA221423DE0001B8682 /* Bluetooth.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = Bluetooth.framework;
+ remoteRef = 6E733FA121423DE0001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FA421423DE0001B8682 /* BluetoothTests.xctest */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.cfbundle;
+ path = BluetoothTests.xctest;
+ remoteRef = 6E733FA321423DE0001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FD021423DEC001B8682 /* GATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = GATT.framework;
+ remoteRef = 6E733FCF21423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FD221423DEC001B8682 /* GATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = GATT.framework;
+ remoteRef = 6E733FD121423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FD421423DEC001B8682 /* GATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = GATT.framework;
+ remoteRef = 6E733FD321423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FD621423DEC001B8682 /* GATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = GATT.framework;
+ remoteRef = 6E733FD521423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FD821423DEC001B8682 /* GATTTests.xctest */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.cfbundle;
+ path = GATTTests.xctest;
+ remoteRef = 6E733FD721423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FDA21423DEC001B8682 /* DarwinGATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = DarwinGATT.framework;
+ remoteRef = 6E733FD921423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FDC21423DEC001B8682 /* DarwinGATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = DarwinGATT.framework;
+ remoteRef = 6E733FDB21423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FDE21423DEC001B8682 /* DarwinGATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = DarwinGATT.framework;
+ remoteRef = 6E733FDD21423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 6E733FE021423DEC001B8682 /* DarwinGATT.framework */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.framework;
+ path = DarwinGATT.framework;
+ remoteRef = 6E733FDF21423DEC001B8682 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+/* End PBXReferenceProxy section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 6E303DFA2142398E0034A6C1 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6E303E0A2142398F0034A6C1 /* LaunchScreen.storyboard in Resources */,
+ 6E303E072142398F0034A6C1 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 6E0596E92158A46500BB43DC /* Increment Build */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Increment Build";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "#!/bin/bash\n\n# update_build_number.sh\n# Usage: `update_build_number.sh [branch]`\n# Run this script after the 'Copy Bundle Resources' build phase\n# Ref: http://tgoode.com/2014/06/05/sensible-way-increment-bundle-version-cfbundleversion-xcode/\n\nif [ $TRAVIS == \"true\" ]\nthen echo \"Updating build number to TRAVISCI.\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion TRAVISCI\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nelse branch=${1:-'master'}\nbuildNumber=$(expr $(git rev-list $branch --count) - $(git rev-list HEAD..$branch --count))\necho \"Updating build number to $buildNumber using branch '$branch'.\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nfi";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 6E303DF82142398E0034A6C1 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6E0596AD2158448B00BB43DC /* CharacteristicViewController.swift in Sources */,
+ 6E303E1921423AED0034A6C1 /* AppDelegate.swift in Sources */,
+ 6EAEEE3E21424717009A7A9F /* ActivityIndicatorViewController.swift in Sources */,
+ 6EAEEE3C2142470C009A7A9F /* ErrorAlert.swift in Sources */,
+ 6EAEEE4021424790009A7A9F /* Async.swift in Sources */,
+ 6E73402C21424437001B8682 /* Central.swift in Sources */,
+ 6E73401F21424322001B8682 /* CentralViewController.swift in Sources */,
+ 6E0596EC2158A88500BB43DC /* Appearance.swift in Sources */,
+ 6EAEEED221437F2B009A7A9F /* ServicesViewController.swift in Sources */,
+ 6EAEEE42214247F3009A7A9F /* Log.swift in Sources */,
+ 6E303E1A21423AED0034A6C1 /* main.swift in Sources */,
+ 51B6F9B3215567E800251524 /* CharacteristicsViewController.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 6E733FE421423DFE001B8682 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = "GATT-iOS";
+ targetProxy = 6E733FE321423DFE001B8682 /* PBXContainerItemProxy */;
+ };
+ 6E733FE821423DFE001B8682 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = "DarwinGATT-iOS";
+ targetProxy = 6E733FE721423DFE001B8682 /* PBXContainerItemProxy */;
+ };
+ 6E733FEC21423DFE001B8682 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = "Bluetooth-iOS";
+ targetProxy = 6E733FEB21423DFE001B8682 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 6E303E082142398F0034A6C1 /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 6E303E092142398F0034A6C1 /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 6E303E0C2142398F0034A6C1 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 6E303E0D2142398F0034A6C1 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 6E303E0F2142398F0034A6C1 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = DGYBYARDK9;
+ INFOPLIST_FILE = BluetoothExplorer/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = org.pureswift.BluetoothExplorer;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 6E303E102142398F0034A6C1 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = DGYBYARDK9;
+ INFOPLIST_FILE = BluetoothExplorer/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = org.pureswift.BluetoothExplorer;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 6E303DF72142398E0034A6C1 /* Build configuration list for PBXProject "BluetoothExplorer" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6E303E0C2142398F0034A6C1 /* Debug */,
+ 6E303E0D2142398F0034A6C1 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 6E303E0E2142398F0034A6C1 /* Build configuration list for PBXNativeTarget "BluetoothExplorer" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6E303E0F2142398F0034A6C1 /* Debug */,
+ 6E303E102142398F0034A6C1 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 6E303DF42142398E0034A6C1 /* Project object */;
+}
diff --git a/BluetoothExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iOS/BluetoothExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 67%
rename from BluetoothExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to iOS/BluetoothExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index a8e5804..97fe2a5 100644
--- a/BluetoothExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/iOS/BluetoothExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -2,6 +2,6 @@
+ location = "self:BluetoothExplorer.xcodeproj">
diff --git a/BluetoothExplorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/BluetoothExplorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from BluetoothExplorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to iOS/BluetoothExplorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/BluetoothExplorer.xcodeproj/xcshareddata/xcschemes/BluetoothExplorer.xcscheme b/iOS/BluetoothExplorer.xcodeproj/xcshareddata/xcschemes/BluetoothExplorer.xcscheme
similarity index 90%
rename from BluetoothExplorer.xcodeproj/xcshareddata/xcschemes/BluetoothExplorer.xcscheme
rename to iOS/BluetoothExplorer.xcodeproj/xcshareddata/xcschemes/BluetoothExplorer.xcscheme
index 8c10d54..cb9fec8 100644
--- a/BluetoothExplorer.xcodeproj/xcshareddata/xcschemes/BluetoothExplorer.xcscheme
+++ b/iOS/BluetoothExplorer.xcodeproj/xcshareddata/xcschemes/BluetoothExplorer.xcscheme
@@ -4,7 +4,7 @@
version = "1.3">
+ buildImplicitDependencies = "NO">
@@ -32,7 +32,7 @@
@@ -49,13 +49,14 @@
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
+ stopOnEveryMainThreadCheckerIssue = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
@@ -74,7 +75,7 @@
runnableDebuggingMode = "0">
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Contents.json
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/Icon@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/ios-marketing.png b/iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/ios-marketing.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/ios-marketing.png
rename to iOS/BluetoothExplorer/Assets.xcassets/AppIcon.appiconset/ios-marketing.png
diff --git a/BluetoothExplorer/Assets.xcassets/Colors/Contents.json b/iOS/BluetoothExplorer/Assets.xcassets/Contents.json
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Colors/Contents.json
rename to iOS/BluetoothExplorer/Assets.xcassets/Contents.json
diff --git a/BluetoothExplorer/Assets.xcassets/Logo.imageset/Contents.json b/iOS/BluetoothExplorer/Assets.xcassets/Logo.imageset/Contents.json
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Logo.imageset/Contents.json
rename to iOS/BluetoothExplorer/Assets.xcassets/Logo.imageset/Contents.json
diff --git a/BluetoothExplorer/Assets.xcassets/Logo.imageset/PureSwiftBluetooth.png b/iOS/BluetoothExplorer/Assets.xcassets/Logo.imageset/PureSwiftBluetooth.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Logo.imageset/PureSwiftBluetooth.png
rename to iOS/BluetoothExplorer/Assets.xcassets/Logo.imageset/PureSwiftBluetooth.png
diff --git a/BluetoothExplorer/Assets.xcassets/Contents.json b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Contents.json
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Contents.json
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Contents.json
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Contents.json b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Contents.json
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Contents.json
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Contents.json
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near.png b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near.png
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near.png
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@3x.png b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@3x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@3x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/Near.imageset/Near@3x.png
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/Contents.json b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/Contents.json
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/Contents.json
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/Contents.json
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected.png b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected.png
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected.png
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@2x.png b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@2x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@2x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@2x.png
diff --git a/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@3x.png b/iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@3x.png
old mode 100644
new mode 100755
similarity index 100%
rename from BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@3x.png
rename to iOS/BluetoothExplorer/Assets.xcassets/Tab Bar/NearSelected.imageset/NearSelected@3x.png
diff --git a/iOS/BluetoothExplorer/Base.lproj/LaunchScreen.storyboard b/iOS/BluetoothExplorer/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f83f6fd
--- /dev/null
+++ b/iOS/BluetoothExplorer/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BluetoothExplorer/Info.plist b/iOS/BluetoothExplorer/Info.plist
similarity index 83%
rename from BluetoothExplorer/Info.plist
rename to iOS/BluetoothExplorer/Info.plist
index fd59672..67d84de 100644
--- a/BluetoothExplorer/Info.plist
+++ b/iOS/BluetoothExplorer/Info.plist
@@ -2,10 +2,10 @@
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
Bluetooth
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -19,24 +19,15 @@
CFBundleShortVersionString
1.0.0
CFBundleVersion
- 1
+ GitVersion
LSRequiresIPhoneOS
- UIBackgroundModes
-
- bluetooth-central
- bluetooth-peripheral
-
UILaunchStoryboardName
LaunchScreen
- UIMainStoryboardFile
- Main
UIRequiredDeviceCapabilities
armv7
- UIStatusBarStyle
- UIStatusBarStyleLightContent
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
diff --git a/rswift b/rswift
deleted file mode 100755
index c51f23f..0000000
Binary files a/rswift and /dev/null differ