Merge pull request #273 from eladdekel/main

Implementation of native iOS app
pull/285/head
killian 7 months ago committed by GitHub
commit c401530a5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,29 @@
# iOS/iPadOS Native Client
This repository contains the source code for the 01 iOS/iPadOS Native app. It is a work in progress and currently has a dedicated development button.
Feel free to improve this and make a pull request!
To run it on your own, you can either install the app directly through the current TestFlight [here](https://testflight.apple.com/join/v8SyuzMT), or build from the source code files in Xcode on your Mac.
## Instructions
Follow the **[software setup steps](https://github.com/OpenInterpreter/01?tab=readme-ov-file#software)** in the main repo's README first before you read this
In Xcode, open the 'zerooone-app' project file in the project folder, change the Signing Team and Bundle Identifier, and build.
## Using the App
To use the app there are four features:
### 1. The speak "Button"
Made to emulate the button on the hardware models of 01, the big, yellow circle in the middle of the screen is what you hold when you want to speak to the model, and let go when you're finished speaking.
### 2. The settings button
Tapping the settings button will allow you to input your websocket address so that the app can properly connect to your computer. If you're not sure how to obtain this, read the **'How to Install'** section below!
### 3. The reconnect button
The arrow will be RED when the websocket connection is not live, and GREEN when it is. If you're making some changes you can easily reconnect by simply tapping the arrow button (or you can just start holding the speak button, too!).
### 4. The terminal button
The terminal button allows you to see all response text coming in from the server side of the 01. You can toggle it by tapping on the button, and each toggle clears the on-device cache of text.

@ -0,0 +1,415 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
750E4C0B2BEDD11C00AEE3B1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750E4C0A2BEDD11C00AEE3B1 /* AppDelegate.swift */; };
750E4C0D2BEDD11C00AEE3B1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750E4C0C2BEDD11C00AEE3B1 /* SceneDelegate.swift */; };
750E4C0F2BEDD11C00AEE3B1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750E4C0E2BEDD11C00AEE3B1 /* ViewController.swift */; };
750E4C122BEDD11C00AEE3B1 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 750E4C112BEDD11C00AEE3B1 /* Base */; };
750E4C142BEDD11D00AEE3B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 750E4C132BEDD11D00AEE3B1 /* Assets.xcassets */; };
750E4C172BEDD11D00AEE3B1 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 750E4C162BEDD11D00AEE3B1 /* Base */; };
750E4C202BEDD16E00AEE3B1 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 750E4C1F2BEDD16E00AEE3B1 /* Starscream */; };
755DC3B22BEE60A7002B66DF /* AudioRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755DC3B12BEE60A7002B66DF /* AudioRecording.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
750E4C072BEDD11C00AEE3B1 /* zeroone-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "zeroone-app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
750E4C0A2BEDD11C00AEE3B1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
750E4C0C2BEDD11C00AEE3B1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
750E4C0E2BEDD11C00AEE3B1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
750E4C112BEDD11C00AEE3B1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
750E4C132BEDD11D00AEE3B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
750E4C162BEDD11D00AEE3B1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
750E4C182BEDD11D00AEE3B1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
755DC3B12BEE60A7002B66DF /* AudioRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecording.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
750E4C042BEDD11C00AEE3B1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
750E4C202BEDD16E00AEE3B1 /* Starscream in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
750E4BFE2BEDD11C00AEE3B1 = {
isa = PBXGroup;
children = (
750E4C092BEDD11C00AEE3B1 /* zeroone-app */,
750E4C082BEDD11C00AEE3B1 /* Products */,
);
sourceTree = "<group>";
};
750E4C082BEDD11C00AEE3B1 /* Products */ = {
isa = PBXGroup;
children = (
750E4C072BEDD11C00AEE3B1 /* zeroone-app.app */,
);
name = Products;
sourceTree = "<group>";
};
750E4C092BEDD11C00AEE3B1 /* zeroone-app */ = {
isa = PBXGroup;
children = (
750E4C0A2BEDD11C00AEE3B1 /* AppDelegate.swift */,
750E4C0C2BEDD11C00AEE3B1 /* SceneDelegate.swift */,
750E4C0E2BEDD11C00AEE3B1 /* ViewController.swift */,
750E4C102BEDD11C00AEE3B1 /* Main.storyboard */,
750E4C132BEDD11D00AEE3B1 /* Assets.xcassets */,
750E4C152BEDD11D00AEE3B1 /* LaunchScreen.storyboard */,
750E4C182BEDD11D00AEE3B1 /* Info.plist */,
755DC3B12BEE60A7002B66DF /* AudioRecording.swift */,
);
path = "zeroone-app";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
750E4C062BEDD11C00AEE3B1 /* zeroone-app */ = {
isa = PBXNativeTarget;
buildConfigurationList = 750E4C1B2BEDD11D00AEE3B1 /* Build configuration list for PBXNativeTarget "zeroone-app" */;
buildPhases = (
750E4C032BEDD11C00AEE3B1 /* Sources */,
750E4C042BEDD11C00AEE3B1 /* Frameworks */,
750E4C052BEDD11C00AEE3B1 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "zeroone-app";
packageProductDependencies = (
750E4C1F2BEDD16E00AEE3B1 /* Starscream */,
);
productName = "zeroone-app";
productReference = 750E4C072BEDD11C00AEE3B1 /* zeroone-app.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
750E4BFF2BEDD11C00AEE3B1 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1530;
LastUpgradeCheck = 1530;
TargetAttributes = {
750E4C062BEDD11C00AEE3B1 = {
CreatedOnToolsVersion = 15.3;
};
};
};
buildConfigurationList = 750E4C022BEDD11C00AEE3B1 /* Build configuration list for PBXProject "zeroone-app" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 750E4BFE2BEDD11C00AEE3B1;
packageReferences = (
750E4C1E2BEDD16D00AEE3B1 /* XCRemoteSwiftPackageReference "Starscream" */,
);
productRefGroup = 750E4C082BEDD11C00AEE3B1 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
750E4C062BEDD11C00AEE3B1 /* zeroone-app */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
750E4C052BEDD11C00AEE3B1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
750E4C142BEDD11D00AEE3B1 /* Assets.xcassets in Resources */,
750E4C172BEDD11D00AEE3B1 /* Base in Resources */,
750E4C122BEDD11C00AEE3B1 /* Base in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
750E4C032BEDD11C00AEE3B1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
750E4C0F2BEDD11C00AEE3B1 /* ViewController.swift in Sources */,
750E4C0B2BEDD11C00AEE3B1 /* AppDelegate.swift in Sources */,
755DC3B22BEE60A7002B66DF /* AudioRecording.swift in Sources */,
750E4C0D2BEDD11C00AEE3B1 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
750E4C102BEDD11C00AEE3B1 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
750E4C112BEDD11C00AEE3B1 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
750E4C152BEDD11D00AEE3B1 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
750E4C162BEDD11D00AEE3B1 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
750E4C192BEDD11D00AEE3B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
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;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
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 = 15.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
750E4C1A2BEDD11D00AEE3B1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
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;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
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 = 15.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
750E4C1C2BEDD11D00AEE3B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = W5NGQJV8X2;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "zeroone-app/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = 01ForiOS;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Audio data from microphone is needed to send commands.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 15;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.ontheroofstudios.zeroone-app";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
750E4C1D2BEDD11D00AEE3B1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = W5NGQJV8X2;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "zeroone-app/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = 01ForiOS;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Audio data from microphone is needed to send commands.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 15;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.ontheroofstudios.zeroone-app";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
750E4C022BEDD11C00AEE3B1 /* Build configuration list for PBXProject "zeroone-app" */ = {
isa = XCConfigurationList;
buildConfigurations = (
750E4C192BEDD11D00AEE3B1 /* Debug */,
750E4C1A2BEDD11D00AEE3B1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
750E4C1B2BEDD11D00AEE3B1 /* Build configuration list for PBXNativeTarget "zeroone-app" */ = {
isa = XCConfigurationList;
buildConfigurations = (
750E4C1C2BEDD11D00AEE3B1 /* Debug */,
750E4C1D2BEDD11D00AEE3B1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
750E4C1E2BEDD16D00AEE3B1 /* XCRemoteSwiftPackageReference "Starscream" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/daltoniam/Starscream";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.8;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
750E4C1F2BEDD16E00AEE3B1 /* Starscream */ = {
isa = XCSwiftPackageProductDependency;
package = 750E4C1E2BEDD16D00AEE3B1 /* XCRemoteSwiftPackageReference "Starscream" */;
productName = Starscream;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 750E4BFF2BEDD11C00AEE3B1 /* Project object */;
}

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
<string>Default</string>
<key>ShowSharedSchemesAutomaticallyEnabled</key>
<true/>
</dict>
</plist>

@ -0,0 +1,36 @@
//
// AppDelegate.swift
// zeroone-app
//
// Created by Elad Dekel on 2024-05-09.
//
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "O.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "vector.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by Pixelmator Pro 3.5.11 -->
<svg width="800" height="800" viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<path id="Ellipse" fill="none" stroke="#000000" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" d="M 800 400 C 800 179.086121 620.913879 0 400 0 C 179.086105 0 0 179.086121 0 400 C 0 620.913879 179.086105 800 400 800 C 620.913879 800 800 620.913879 800 400 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 460 B

@ -0,0 +1,67 @@
//
// AudioRecording.swift
// zeroone-app
//
// Created by Elad Dekel on 2024-05-10.
//
import Foundation
import AVFoundation
class AudioRecording: NSObject, AVAudioRecorderDelegate {
var recorder: AVAudioRecorder!
var session: AVAudioSession!
var isRecording = false
func startRecording() {
session = AVAudioSession()
let audio = getDocumentsDirectory().appendingPathComponent("tempvoice.wav") // indicates where the audio data will be recording to
let s: [String: Any] = [
AVFormatIDKey: kAudioFormatLinearPCM,
AVSampleRateKey: 16000.0,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
recorder = try AVAudioRecorder(url: audio, settings: s)
try session.setActive(true)
try session.setCategory(.playAndRecord, mode: .default)
recorder!.delegate = self
recorder!.record()
isRecording = true
} catch {
print("Error recording")
print(error.localizedDescription)
}
}
func getDocumentsDirectory() -> URL { // big thanks to twostraws for this helper function (hackingwithswift.com)
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func stopRecording() -> Data? {
if isRecording && recorder != nil {
recorder!.stop()
let audio = getDocumentsDirectory().appendingPathComponent("tempvoice.wav")
recorder = nil
do {
let data = try Data(contentsOf: audio) // sends raw audio data
try FileManager.default.removeItem(at: audio) // deletes the file
return data
} catch {
print(error.localizedDescription)
return nil
}
} else {
print("not recording")
return nil
}
}
}

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="zeroone_app" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="circle.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="8GG-Ei-Zce">
<rect key="frame" x="79" y="304.66666666666669" width="245" height="243.66666666666657"/>
<color key="tintColor" systemColor="systemYellowColor"/>
<constraints>
<constraint firstAttribute="height" constant="245" id="ZNG-mL-QWz"/>
<constraint firstAttribute="width" constant="245" id="kvy-0q-8ID"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="gJn-v2-d0Z">
<rect key="frame" x="49" y="131" width="303.66666666666669" height="92.666666666666686"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" spacing="39" translatesAutoresizingMaskIntoConstraints="NO" id="Jep-bq-nBz">
<rect key="frame" x="95.999999999999986" y="780.33333333333337" width="209.66666666666663" height="37.666666666666629"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" image="gear" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="4J4-Vq-uXz">
<rect key="frame" x="0.0" y="-5" width="47.666666666666664" height="47.333333333333329"/>
<color key="tintColor" systemColor="labelColor"/>
<constraints>
<constraint firstAttribute="width" constant="47.666666666666664" id="RUe-bq-jRC"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="42" id="yem-fm-8uw"/>
</constraints>
<preferredSymbolConfiguration key="preferredSymbolConfiguration" configurationType="pointSize" pointSize="25" scale="large"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrow.clockwise" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="sTV-gO-axp">
<rect key="frame" x="86.666666666666657" y="-1.3333333333333321" width="42" height="37.333333333333329"/>
<color key="tintColor" systemColor="labelColor"/>
<constraints>
<constraint firstAttribute="width" constant="42" id="gYc-1f-wW5"/>
</constraints>
<preferredSymbolConfiguration key="preferredSymbolConfiguration" configurationType="pointSize" pointSize="30"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="terminal" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="Czp-9u-tDH">
<rect key="frame" x="167.66666666666669" y="2.6666666666666679" width="42" height="32.666666666666657"/>
<color key="tintColor" systemColor="labelColor"/>
<constraints>
<constraint firstAttribute="width" constant="42" id="ZZC-ol-tbv"/>
</constraints>
<preferredSymbolConfiguration key="preferredSymbolConfiguration" configurationType="pointSize" pointSize="30"/>
</imageView>
</subviews>
<constraints>
<constraint firstAttribute="width" secondItem="Jep-bq-nBz" secondAttribute="height" multiplier="50:9" id="3oj-ZY-vQc"/>
</constraints>
</stackView>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" alpha="0.0" contentMode="scaleToFill" keyboardDismissMode="interactive" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Vqf-Pz-bQv">
<rect key="frame" x="40" y="95" width="322.66666666666669" height="394"/>
<color key="backgroundColor" systemColor="systemGray6Color"/>
<constraints>
<constraint firstAttribute="width" secondItem="Vqf-Pz-bQv" secondAttribute="height" multiplier="307:375" id="i7c-pN-3Yk"/>
</constraints>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="8GG-Ei-Zce" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="2aD-gd-oFQ"/>
<constraint firstItem="Jep-bq-nBz" firstAttribute="bottom" secondItem="6Tk-OE-BBY" secondAttribute="bottom" id="4rh-NU-Qkg"/>
<constraint firstItem="Vqf-Pz-bQv" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="40" id="OLg-bI-RLB"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="Jep-bq-nBz" secondAttribute="trailing" constant="97" id="Wc2-3u-fpo"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="Vqf-Pz-bQv" secondAttribute="trailing" constant="40" id="cxX-KY-p3B"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="gJn-v2-d0Z" secondAttribute="trailing" constant="50" id="iNY-a6-Ww4"/>
<constraint firstItem="gJn-v2-d0Z" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="49" id="owN-up-6fV"/>
<constraint firstItem="8GG-Ei-Zce" firstAttribute="top" secondItem="gJn-v2-d0Z" secondAttribute="bottom" constant="80" id="pKl-kg-6Qv"/>
<constraint firstItem="8GG-Ei-Zce" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="qrj-Tv-TZB"/>
<constraint firstItem="Vqf-Pz-bQv" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" constant="36" id="sST-jo-Jg6"/>
<constraint firstItem="gJn-v2-d0Z" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" constant="72" id="wFg-9E-IMx"/>
<constraint firstItem="Jep-bq-nBz" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="96" id="zZy-C5-TdW"/>
</constraints>
</view>
<connections>
<outlet property="circle" destination="8GG-Ei-Zce" id="Q6Q-TY-A1z"/>
<outlet property="infoText" destination="gJn-v2-d0Z" id="pR2-Ps-nKF"/>
<outlet property="reconnectIcon" destination="sTV-gO-axp" id="iuS-Zj-2cd"/>
<outlet property="settingsGear" destination="4J4-Vq-uXz" id="vgy-Jz-tEd"/>
<outlet property="terminalButton" destination="Czp-9u-tDH" id="nhQ-4o-UHd"/>
<outlet property="terminalFeed" destination="Vqf-Pz-bQv" id="h3N-1T-wNf"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="46.564885496183201" y="3.5211267605633805"/>
</scene>
</scenes>
<resources>
<image name="arrow.clockwise" catalog="system" width="113" height="128"/>
<image name="circle.fill" catalog="system" width="128" height="123"/>
<image name="gear" catalog="system" width="128" height="122"/>
<image name="terminal" catalog="system" width="128" height="93"/>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemGray6Color">
<color red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemYellowColor">
<color red="1" green="0.80000000000000004" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Your audio is used the convert your voice requests into text requests.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>

@ -0,0 +1,52 @@
//
// SceneDelegate.swift
// zeroone-app
//
// Created by Elad Dekel on 2024-05-09.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}

@ -0,0 +1,366 @@
//
// ViewController.swift
// zeroone-app
//
// Created by Elad Dekel on 2024-05-09.
//
import UIKit
import Starscream
import AVFoundation
class ViewController: UIViewController, WebSocketDelegate {
@IBOutlet weak var terminalFeed: UITextView!
@IBOutlet weak var terminalButton: UIImageView!
@IBOutlet weak var reconnectIcon: UIImageView!
@IBOutlet weak var circle: UIImageView!
@IBOutlet weak var settingsGear: UIImageView!
@IBOutlet weak var infoText: UILabel!
var audioRecordingInstance: AudioRecording?
private var audioData = Data()
private var audioPlayer: AVAudioPlayer?
var address: String?
var isConnected = false
var recordingPermission = false
var terminal = false
var socket: WebSocket?
override func viewDidLoad() {
super.viewDidLoad()
terminalFeed.layer.cornerRadius = 15
infoText.text = "Hold to start once connected."
// Create a gesture recognizer that tracks when the "button" is held
let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(buttonPress(_:)))
pressGesture.minimumPressDuration = 0.01
circle.addGestureRecognizer(pressGesture)
circle.isUserInteractionEnabled = true
circle.translatesAutoresizingMaskIntoConstraints = false
// Create a geature recognizer for the settings button
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(settingsGear(_:)))
settingsGear.addGestureRecognizer(tapGesture)
settingsGear.isUserInteractionEnabled = true
let reconnectGesture = UITapGestureRecognizer(target: self, action: #selector(recconectIcon(_:)))
reconnectIcon.addGestureRecognizer(reconnectGesture)
reconnectIcon.isUserInteractionEnabled = true
let terminal = UITapGestureRecognizer(target: self, action: #selector(terminalIcon(_:)))
terminalButton.addGestureRecognizer(terminal)
terminalButton.isUserInteractionEnabled = true
}
func checkRecordingPerms() {
let sess = AVAudioSession.sharedInstance()
switch (sess.recordPermission) {
case.denied, .undetermined:
sess.requestRecordPermission { (granted) in
if granted {
self.recordingPermission = true
} else {
let alert = UIAlertController(title: "Recording Not Permitted", message: "You must allow audio recording in order to send commands. Close the app and re-open it to try again.", preferredStyle: .alert)
let action = UIAlertAction(title: "Understood", style: .default)
alert.addAction(action)
self.present(alert, animated: true)
}
}
case .granted:
recordingPermission = true
default:
break
}
}
override func viewDidAppear(_ animated: Bool) {
if ((UserDefaults.standard.value(forKey: "IPINFO")) != nil) {
print("here")
address = UserDefaults.standard.string(forKey: "IPINFO")
establishConnection()
} else {
print("there")
setAddress()
}
checkRecordingPerms()
}
func receieved(data: String) {
infoText.text = data
}
func setAddress() {
let alert = UIAlertController(title: "Set the Address", message: "Input the address of the WebSocket (found in the terminal running 01 software)", preferredStyle: .alert)
alert.addTextField { (field) in
field.placeholder = "Enter Address Here"
}
let cancelButton = UIAlertAction(title: "Cancel", style: .cancel)
alert.addAction(cancelButton)
let submitButton = UIAlertAction(title: "Done", style: .default) { (_) in
if let field = alert.textFields?.first, let text = field.text {
UserDefaults.standard.setValue(text, forKey: "IPINFO")
self.address = text
self.establishConnection()
// HAVE THE TEXT FIELD
}
}
alert.addAction(submitButton)
present(alert, animated: true)
}
@objc func recconectIcon(_ sender: UIGestureRecognizer) {
infoText.text = ""
self.establishConnection()
}
@objc func terminalIcon(_ sender: UIGestureRecognizer) {
if (terminal) {
UIView.animate(withDuration: 0.3) {
self.terminalFeed.text = ""
self.terminalFeed.alpha = 0
let moveT = CGAffineTransform(translationX: 0, y: -190)
self.appendTranslation(transform: moveT)
self.terminalButton.image = UIImage(systemName: "apple.terminal")
} completion: { done in
self.terminal = false
}
} else {
UIView.animate(withDuration: 0.3) {
self.terminalFeed.alpha = 1
let moveT = CGAffineTransform(translationX: 0, y: 190)
self.appendTranslation(transform: moveT)
self.terminalButton.image = UIImage(systemName: "apple.terminal.fill")
} completion: { done in
self.terminal = true
}
}
}
@objc func settingsGear(_ sender: UIGestureRecognizer) {
infoText.text = ""
setAddress()
}
func appendTranslation(transform: CGAffineTransform) {
var currentTransform = self.circle.transform
currentTransform = currentTransform.concatenating(transform)
self.circle.transform = currentTransform
}
@objc func buttonPress(_ sender: UILongPressGestureRecognizer) {
infoText.text = ""
let feedback = UIImpactFeedbackGenerator(style: .medium)
if sender.state == .began {
socket?.connect()
// check for recording permission, if exists
// it began, start recording!
if (isConnected && recordingPermission) {
audioRecordingInstance = AudioRecording()
audioRecordingInstance!.startRecording()
infoText.text = ""
UIView.animate(withDuration: 0.1) {
self.circle.tintColor = .green
let newT = CGAffineTransform(scaleX: 0.7, y: 0.7)
self.appendTranslation(transform: newT)
feedback.prepare()
feedback.impactOccurred()
}
} else {
let errorFeedback = UIImpactFeedbackGenerator(style: .heavy)
errorFeedback.prepare()
errorFeedback.impactOccurred()
if (isConnected && !recordingPermission) {
infoText.text = "Not recording permission. Please close and re-open the app."
} else {
infoText.text = "Not connected."
establishConnection()
}
UIView.animate(withDuration: 0.5) {
self.circle.tintColor = .red
} completion: { _ in
self.circle.tintColor = .systemYellow
}
}
} else if sender.state == .ended {
if (isConnected && recordingPermission) {
if (audioRecordingInstance != nil) {
let response = audioRecordingInstance!.stopRecording()
if (response != nil) {
sendAudio(audio: response!)
}
UIView.animate(withDuration: 0.1) {
self.circle.tintColor = .systemYellow
let newT = CGAffineTransform(scaleX: 1.4, y: 1.4)
self.appendTranslation(transform: newT)
feedback.prepare()
feedback.impactOccurred()
}
}
}
// stop recording and send the audio
}
}
func establishConnection() { //connect to the web socket
if (address != nil) {
var request = URLRequest(url: URL(string: "http://\(address!)")!)
request.timeoutInterval = 5
socket = WebSocket(request: request)
socket!.delegate = self
socket!.connect()
} else {
setAddress()
}
}
func didReceive(event: Starscream.WebSocketEvent, client: any Starscream.WebSocketClient) { // deal with receiving data from websocket
switch event {
case .connected( _):
isConnected = true
reconnectIcon.tintColor = .green
case .disconnected(let reason, let code):
isConnected = false
reconnectIcon.tintColor = .red
case .text(let string):
if (terminal) {
terminalFeed.text = terminalFeed.text + "\n>> \(string)"
let range = NSMakeRange(terminalFeed.text.count - 1, 0)
terminalFeed.scrollRangeToVisible(range)
}
if (string.contains("audio") && string.contains("bytes.raw") && string.contains("start")) {
infoText.text = "Receiving response..."
// it started collecting data!
print("Audio is being receieved.")
} else if (string.contains("audio") && string.contains("bytes.raw") && string.contains("end")) {
infoText.text = ""
print("Audio is no longer being receieved.")
let wavHeader = createWAVHeader(audioDataSize: Int32(audioData.count - 44))
// Combine header and data
var completeWAVData = Data()
completeWAVData.append(wavHeader)
completeWAVData.append(audioData.subdata(in: 44..<audioData.count))
do {
try audioPlayer = AVAudioPlayer(data: completeWAVData)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch {
print("Error")
}
}
print("Received text: \(string)")
case .binary(let data):
audioData.append(data)
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
isConnected = false
reconnectIcon.tintColor = .red
case .error(_):
isConnected = false
reconnectIcon.tintColor = .red
case .peerClosed:
isConnected = false
reconnectIcon.tintColor = .red
break
}
}
func createWAVHeader(audioDataSize: Int32) -> Data {
let headerSize: Int32 = 44 // Standard WAV header size
let chunkSize: Int32 = 36 + audioDataSize
let sampleRate: Int32 = 16000 // From i2s_config
let numChannels: Int16 = 1 // From i2s_config (mono)
let bitsPerSample: Int16 = 16 // From i2s_config
let byteRate: Int32 = sampleRate * Int32(numChannels) * Int32(bitsPerSample) / 8
let blockAlign: Int16 = numChannels * bitsPerSample / 8
var headerData = Data()
// RIFF Chunk
headerData.append(stringToData("RIFF")) // ChunkID
headerData.append(int32ToData(chunkSize)) // ChunkSize
headerData.append(stringToData("WAVE")) // Format
// fmt Subchunk
headerData.append(stringToData("fmt ")) // Subchunk1ID
headerData.append(int32ToData(16)) // Subchunk1Size (16 for PCM)
headerData.append(int16ToData(1)) // AudioFormat (1 for PCM)
headerData.append(int16ToData(numChannels)) // NumChannels
headerData.append(int32ToData(sampleRate)) // SampleRate
headerData.append(int32ToData(byteRate)) // ByteRate
headerData.append(int16ToData(blockAlign)) // BlockAlign
headerData.append(int16ToData(bitsPerSample)) // BitsPerSample
// data Subchunk
headerData.append(stringToData("data")) // Subchunk2ID
headerData.append(int32ToData(audioDataSize)) // Subchunk2Size
return headerData
}
func stringToData(_ string: String) -> Data {
return string.data(using: .utf8)!
}
func int16ToData(_ value: Int16) -> Data {
var value = value.littleEndian
return Data(bytes: &value, count: MemoryLayout<Int16>.size)
}
func int32ToData(_ value: Int32) -> Data {
var value = value.littleEndian
return Data(bytes: &value, count: MemoryLayout<Int32>.size)
}
func sendAudio(audio: Data) {
if (isConnected) {
socket!.write(string: "{\"role\": \"user\", \"type\": \"audio\", \"format\": \"bytes.raw\", \"start\": true}")
socket!.write(data: audio)
socket!.write(string: "{\"role\": \"user\", \"type\": \"audio\", \"format\": \"bytes.raw\", \"end\": true}")
} else {
print("Not connected!")
}
}
}
Loading…
Cancel
Save