diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.pbxproj b/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.pbxproj index e8d4623..52fa64b 100644 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.pbxproj +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.pbxproj @@ -6,63 +6,20 @@ objectVersion = 77; objects = { -/* Begin PBXContainerItemProxy section */ - 67430CA12C9CE2A9000F4E76 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 67430C872C9CE2A7000F4E76 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 67430C8E2C9CE2A8000F4E76; - remoteInfo = "Ammaar's Dorm Controls"; - }; - 67430CAB2C9CE2A9000F4E76 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 67430C872C9CE2A7000F4E76 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 67430C8E2C9CE2A8000F4E76; - remoteInfo = "Ammaar's Dorm Controls"; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ - 67430C8F2C9CE2A8000F4E76 /* Ammaar's Dorm Controls.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ammaar's Dorm Controls.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 67430CA02C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Ammaar's Dorm ControlsTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 67430CAA2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Ammaar's Dorm ControlsUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 67C97DF72D0542BE005E36E6 /* Ammaar's Dorm Controls.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ammaar's Dorm Controls.app"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - 67430C912C9CE2A8000F4E76 /* Ammaar's Dorm Controls */ = { + 67C97DF92D0542BE005E36E6 /* Ammaar's Dorm Controls */ = { isa = PBXFileSystemSynchronizedRootGroup; path = "Ammaar's Dorm Controls"; sourceTree = ""; }; - 67430CA32C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = "Ammaar's Dorm ControlsTests"; - sourceTree = ""; - }; - 67430CAD2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = "Ammaar's Dorm ControlsUITests"; - sourceTree = ""; - }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ - 67430C8C2C9CE2A8000F4E76 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 67430C9D2C9CE2A9000F4E76 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 67430CA72C9CE2A9000F4E76 /* Frameworks */ = { + 67C97DF42D0542BE005E36E6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -72,22 +29,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 67430C862C9CE2A7000F4E76 = { + 67C97DEE2D0542BE005E36E6 = { isa = PBXGroup; children = ( - 67430C912C9CE2A8000F4E76 /* Ammaar's Dorm Controls */, - 67430CA32C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests */, - 67430CAD2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests */, - 67430C902C9CE2A8000F4E76 /* Products */, + 67C97DF92D0542BE005E36E6 /* Ammaar's Dorm Controls */, + 67C97DF82D0542BE005E36E6 /* Products */, ); sourceTree = ""; }; - 67430C902C9CE2A8000F4E76 /* Products */ = { + 67C97DF82D0542BE005E36E6 /* Products */ = { isa = PBXGroup; children = ( - 67430C8F2C9CE2A8000F4E76 /* Ammaar's Dorm Controls.app */, - 67430CA02C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests.xctest */, - 67430CAA2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests.xctest */, + 67C97DF72D0542BE005E36E6 /* Ammaar's Dorm Controls.app */, ); name = Products; sourceTree = ""; @@ -95,134 +48,64 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 67430C8E2C9CE2A8000F4E76 /* Ammaar's Dorm Controls */ = { + 67C97DF62D0542BE005E36E6 /* Ammaar's Dorm Controls */ = { isa = PBXNativeTarget; - buildConfigurationList = 67430CB42C9CE2A9000F4E76 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm Controls" */; + buildConfigurationList = 67C97E062D0542BF005E36E6 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm Controls" */; buildPhases = ( - 67430C8B2C9CE2A8000F4E76 /* Sources */, - 67430C8C2C9CE2A8000F4E76 /* Frameworks */, - 67430C8D2C9CE2A8000F4E76 /* Resources */, + 67C97DF32D0542BE005E36E6 /* Sources */, + 67C97DF42D0542BE005E36E6 /* Frameworks */, + 67C97DF52D0542BE005E36E6 /* Resources */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( - 67430C912C9CE2A8000F4E76 /* Ammaar's Dorm Controls */, + 67C97DF92D0542BE005E36E6 /* Ammaar's Dorm Controls */, ); name = "Ammaar's Dorm Controls"; packageProductDependencies = ( ); productName = "Ammaar's Dorm Controls"; - productReference = 67430C8F2C9CE2A8000F4E76 /* Ammaar's Dorm Controls.app */; + productReference = 67C97DF72D0542BE005E36E6 /* Ammaar's Dorm Controls.app */; productType = "com.apple.product-type.application"; }; - 67430C9F2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 67430CB72C9CE2A9000F4E76 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm ControlsTests" */; - buildPhases = ( - 67430C9C2C9CE2A9000F4E76 /* Sources */, - 67430C9D2C9CE2A9000F4E76 /* Frameworks */, - 67430C9E2C9CE2A9000F4E76 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 67430CA22C9CE2A9000F4E76 /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - 67430CA32C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests */, - ); - name = "Ammaar's Dorm ControlsTests"; - packageProductDependencies = ( - ); - productName = "Ammaar's Dorm ControlsTests"; - productReference = 67430CA02C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 67430CA92C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 67430CBA2C9CE2A9000F4E76 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm ControlsUITests" */; - buildPhases = ( - 67430CA62C9CE2A9000F4E76 /* Sources */, - 67430CA72C9CE2A9000F4E76 /* Frameworks */, - 67430CA82C9CE2A9000F4E76 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 67430CAC2C9CE2A9000F4E76 /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - 67430CAD2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests */, - ); - name = "Ammaar's Dorm ControlsUITests"; - packageProductDependencies = ( - ); - productName = "Ammaar's Dorm ControlsUITests"; - productReference = 67430CAA2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - 67430C872C9CE2A7000F4E76 /* Project object */ = { + 67C97DEF2D0542BE005E36E6 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1610; LastUpgradeCheck = 1610; TargetAttributes = { - 67430C8E2C9CE2A8000F4E76 = { + 67C97DF62D0542BE005E36E6 = { CreatedOnToolsVersion = 16.1; }; - 67430C9F2C9CE2A9000F4E76 = { - CreatedOnToolsVersion = 16.1; - TestTargetID = 67430C8E2C9CE2A8000F4E76; - }; - 67430CA92C9CE2A9000F4E76 = { - CreatedOnToolsVersion = 16.1; - TestTargetID = 67430C8E2C9CE2A8000F4E76; - }; }; }; - buildConfigurationList = 67430C8A2C9CE2A7000F4E76 /* Build configuration list for PBXProject "Ammaar's Dorm Controls" */; + buildConfigurationList = 67C97DF22D0542BE005E36E6 /* Build configuration list for PBXProject "Ammaar's Dorm Controls" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); - mainGroup = 67430C862C9CE2A7000F4E76; + mainGroup = 67C97DEE2D0542BE005E36E6; minimizedProjectReferenceProxies = 1; preferredProjectObjectVersion = 77; - productRefGroup = 67430C902C9CE2A8000F4E76 /* Products */; + productRefGroup = 67C97DF82D0542BE005E36E6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 67430C8E2C9CE2A8000F4E76 /* Ammaar's Dorm Controls */, - 67430C9F2C9CE2A9000F4E76 /* Ammaar's Dorm ControlsTests */, - 67430CA92C9CE2A9000F4E76 /* Ammaar's Dorm ControlsUITests */, + 67C97DF62D0542BE005E36E6 /* Ammaar's Dorm Controls */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 67430C8D2C9CE2A8000F4E76 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 67430C9E2C9CE2A9000F4E76 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 67430CA82C9CE2A9000F4E76 /* Resources */ = { + 67C97DF52D0542BE005E36E6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -232,21 +115,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 67430C8B2C9CE2A8000F4E76 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 67430C9C2C9CE2A9000F4E76 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 67430CA62C9CE2A9000F4E76 /* Sources */ = { + 67C97DF32D0542BE005E36E6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -255,21 +124,8 @@ }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - 67430CA22C9CE2A9000F4E76 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 67430C8E2C9CE2A8000F4E76 /* Ammaar's Dorm Controls */; - targetProxy = 67430CA12C9CE2A9000F4E76 /* PBXContainerItemProxy */; - }; - 67430CAC2C9CE2A9000F4E76 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 67430C8E2C9CE2A8000F4E76 /* Ammaar's Dorm Controls */; - targetProxy = 67430CAB2C9CE2A9000F4E76 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin XCBuildConfiguration section */ - 67430CB22C9CE2A9000F4E76 /* Debug */ = { + 67C97E042D0542BF005E36E6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -330,7 +186,7 @@ }; name = Debug; }; - 67430CB32C9CE2A9000F4E76 /* Release */ = { + 67C97E052D0542BF005E36E6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -383,23 +239,29 @@ }; name = Release; }; - 67430CB52C9CE2A9000F4E76 /* Debug */ = { + 67C97E072D0542BF005E36E6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Ammaar's Dorm Controls/Ammaar_s_Dorm_Controls.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Ammaar's Dorm Controls/Preview Content\""; + DEVELOPMENT_TEAM = VM6477A6M8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Ammaar-s-Dorm-Controls-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Ammaar's Dorm Controls"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -407,36 +269,44 @@ IPHONEOS_DEPLOYMENT_TARGET = 18.1; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 15.0; + MACOSX_DEPLOYMENT_TARGET = 15.1; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "AmmaarAlam.Ammaar-s-Dorm-Controls"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; + TARGETED_DEVICE_FAMILY = "1,2"; XROS_DEPLOYMENT_TARGET = 2.1; }; name = Debug; }; - 67430CB62C9CE2A9000F4E76 /* Release */ = { + 67C97E082D0542BF005E36E6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Ammaar's Dorm Controls/Ammaar_s_Dorm_Controls.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Ammaar's Dorm Controls/Preview Content\""; + DEVELOPMENT_TEAM = VM6477A6M8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Ammaar-s-Dorm-Controls-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Ammaar's Dorm Controls"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -444,101 +314,17 @@ IPHONEOS_DEPLOYMENT_TARGET = 18.1; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 15.0; + MACOSX_DEPLOYMENT_TARGET = 15.1; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "AmmaarAlam.Ammaar-s-Dorm-Controls"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; - XROS_DEPLOYMENT_TARGET = 2.1; - }; - name = Release; - }; - 67430CB82C9CE2A9000F4E76 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.1; - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "AmmaarAlam.Ammaar-s-Dorm-ControlsTests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ammaar's Dorm Controls.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Ammaar's Dorm Controls"; - XROS_DEPLOYMENT_TARGET = 2.1; - }; - name = Debug; - }; - 67430CB92C9CE2A9000F4E76 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.1; - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "AmmaarAlam.Ammaar-s-Dorm-ControlsTests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ammaar's Dorm Controls.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Ammaar's Dorm Controls"; - XROS_DEPLOYMENT_TARGET = 2.1; - }; - name = Release; - }; - 67430CBB2C9CE2A9000F4E76 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.1; - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "AmmaarAlam.Ammaar-s-Dorm-ControlsUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_TARGET_NAME = "Ammaar's Dorm Controls"; - XROS_DEPLOYMENT_TARGET = 2.1; - }; - name = Debug; - }; - 67430CBC2C9CE2A9000F4E76 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.1; - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "AmmaarAlam.Ammaar-s-Dorm-ControlsUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_TARGET_NAME = "Ammaar's Dorm Controls"; + TARGETED_DEVICE_FAMILY = "1,2"; XROS_DEPLOYMENT_TARGET = 2.1; }; name = Release; @@ -546,43 +332,25 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 67430C8A2C9CE2A7000F4E76 /* Build configuration list for PBXProject "Ammaar's Dorm Controls" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 67430CB22C9CE2A9000F4E76 /* Debug */, - 67430CB32C9CE2A9000F4E76 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 67430CB42C9CE2A9000F4E76 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm Controls" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 67430CB52C9CE2A9000F4E76 /* Debug */, - 67430CB62C9CE2A9000F4E76 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 67430CB72C9CE2A9000F4E76 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm ControlsTests" */ = { + 67C97DF22D0542BE005E36E6 /* Build configuration list for PBXProject "Ammaar's Dorm Controls" */ = { isa = XCConfigurationList; buildConfigurations = ( - 67430CB82C9CE2A9000F4E76 /* Debug */, - 67430CB92C9CE2A9000F4E76 /* Release */, + 67C97E042D0542BF005E36E6 /* Debug */, + 67C97E052D0542BF005E36E6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 67430CBA2C9CE2A9000F4E76 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm ControlsUITests" */ = { + 67C97E062D0542BF005E36E6 /* Build configuration list for PBXNativeTarget "Ammaar's Dorm Controls" */ = { isa = XCConfigurationList; buildConfigurations = ( - 67430CBB2C9CE2A9000F4E76 /* Debug */, - 67430CBC2C9CE2A9000F4E76 /* Release */, + 67C97E072D0542BF005E36E6 /* Debug */, + 67C97E082D0542BF005E36E6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = 67430C872C9CE2A7000F4E76 /* Project object */; + rootObject = 67C97DEF2D0542BE005E36E6 /* Project object */; } diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.xcworkspace/xcuserdata/alam.xcuserdatad/IDEFindNavigatorScopes.plist b/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.xcworkspace/xcuserdata/alam.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.xcworkspace/xcuserdata/alam.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.xcworkspace/xcuserdata/alam.xcuserdatad/UserInterfaceState.xcuserstate b/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.xcworkspace/xcuserdata/alam.xcuserdatad/UserInterfaceState.xcuserstate index df9d854..2fd20a1 100644 Binary files a/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.xcworkspace/xcuserdata/alam.xcuserdatad/UserInterfaceState.xcuserstate and b/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/project.xcworkspace/xcuserdata/alam.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/xcuserdata/alam.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/xcuserdata/alam.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist deleted file mode 100644 index ac823b2..0000000 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls.xcodeproj/xcuserdata/alam.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Ammaar_s_Dorm_ControlsApp.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Ammaar_s_Dorm_ControlsApp.swift deleted file mode 100644 index 4e5925b..0000000 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Ammaar_s_Dorm_ControlsApp.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Ammaar_s_Dorm_ControlsApp.swift -// Ammaar's Dorm Controls -// -// Created by Ammaar Alam on 9/19/24. -// - -import SwiftUI - -@main -struct Ammaar_s_Dorm_ControlsApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/AppDelegate.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/AppDelegate.swift new file mode 100644 index 0000000..018538c --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/AppDelegate.swift @@ -0,0 +1,15 @@ +import UIKit +import SwiftUI + +class AppDelegate: NSObject, UIApplicationDelegate { + // The shortcut handling code for older iOS versions or the original code snippet + // has been removed because we are using SceneDelegate for handling quick actions + // in iOS 13+. + // + // This ensures no placeholders or duplicate code. The scene delegate will handle + // shortcuts, so no changes are needed here. + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/AppIntents.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/AppIntents.swift new file mode 100644 index 0000000..84182fd --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/AppIntents.swift @@ -0,0 +1,146 @@ +import AppIntents +import Foundation + +@available(iOS 17.0, *) +enum DoorAction: String, CaseIterable, AppEnum { + static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Door Action") + + case open = "open" + case close = "close" + case emergencyClose = "emergency-close" + + static var caseDisplayRepresentations: [DoorAction: DisplayRepresentation] = [ + .open: DisplayRepresentation( + title: "Open Door", + subtitle: "Opens the dorm door", + image: .init(systemName: "arrow.up.circle") + ), + .close: DisplayRepresentation( + title: "Close Door", + subtitle: "Closes the dorm door", + image: .init(systemName: "lock") + ), + .emergencyClose: DisplayRepresentation( + title: "Emergency Close", + subtitle: "Force closes/untangles the door", + image: .init(systemName: "exclamationmark.triangle") + ) + ] +} + +@available(iOS 17.0, *) +struct DoorActionIntent: AppIntent { + static var title: LocalizedStringResource = "Perform Door Action" + static var description = IntentDescription("Choose an action to perform on the door (open, close, or emergency close).") + + @Parameter(title: "Action", default: .open) + var action: DoorAction + + func perform() async throws -> some IntentResult & ProvidesDialog { + try await performDoorCommand(action.rawValue) + return .result(dialog: "Performed \(action) action on the door.") + } +} + +@available(iOS 17.0, *) +struct OpenDoorIntent: AppIntent { + static var title: LocalizedStringResource = "Open Door" + static var description = IntentDescription("Opens the dorm door immediately.") + + func perform() async throws -> some IntentResult & ProvidesDialog { + try await performDoorCommand("open") + return .result(dialog: "Door opened successfully!") + } +} + +@available(iOS 17.0, *) +struct OpenDoorIn3SecIntent: AppIntent { + static var title: LocalizedStringResource = "Open Door (3 Seconds)" + static var description = IntentDescription("Opens the door immediately, waits 3 seconds, then closes it.") + + func perform() async throws -> some IntentResult & ProvidesDialog { + try await performDoorCommand("open") + try await Task.sleep(nanoseconds: 3_000_000_000) + try await performDoorCommand("close") + return .result(dialog: "Opened, waited 3 seconds, and closed the door.") + } +} + +@available(iOS 17.0, *) +struct CloseDoorIntent: AppIntent { + static var title: LocalizedStringResource = "Close Door" + static var description = IntentDescription("Closes the dorm door.") + + func perform() async throws -> some IntentResult & ProvidesDialog { + try await performDoorCommand("close") + return .result(dialog: "Door closed successfully!") + } +} + +@available(iOS 17.0, *) +struct GetDoorStatusIntent: AppIntent, WidgetConfigurationIntent { + static var title: LocalizedStringResource = "Door Status Configuration" + static var description = IntentDescription("Configures the door status widget.") + // No perform method needed. +} + +@available(iOS 17.0, *) +extension AppIntent { + func performDoorCommand(_ command: String) async throws { + try await withCheckedThrowingContinuation { continuation in + NetworkManager.shared.sendCommand(command: command) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + func getDoorStatus() async throws -> String { + try await withCheckedThrowingContinuation { continuation in + NetworkManager.shared.fetchDoorStatus { result in + switch result { + case .success(let status): + continuation.resume(returning: status.doorOpen ? "Door is Open" : "Door is Closed") + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } +} + +@available(iOS 17.0, *) +struct DoorShortcutsProvider: AppShortcutsProvider { + static var appShortcuts: [AppShortcut] { + return [ + AppShortcut( + intent: OpenDoorIn3SecIntent(), + phrases: ["Open door in three seconds"], + shortTitle: "Open in 3s", + systemImageName: "timer" + ), + AppShortcut( + intent: OpenDoorIntent(), + phrases: ["Open door now"], + shortTitle: "Open Now", + systemImageName: "arrow.up.circle" + ), + AppShortcut( + intent: CloseDoorIntent(), + phrases: ["Close the door"], + shortTitle: "Close Door", + systemImageName: "lock" + ), + AppShortcut( + intent: DoorActionIntent(), + phrases: ["Perform a door action"], + shortTitle: "Perform Door Action", + systemImageName: "gear" + ) + ] + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ContentView.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ContentView.swift deleted file mode 100644 index 3136f13..0000000 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ContentView.swift +++ /dev/null @@ -1,35 +0,0 @@ -import SwiftUI - -struct ContentView: View { - @StateObject private var viewModel = DoorControlViewModel() - - var body: some View { - NavigationView { - VStack(spacing: 30) { - StatusView(isDoorOpen: viewModel.isDoorOpen) - - DoorControlView(viewModel: viewModel) - - if viewModel.isLoading { - ProgressView("Processing...") - } - - Spacer() - } - .padding() - .navigationTitle("Ammaar's Dorm Door") - .onAppear { - viewModel.fetchStatus() - } - .alert(item: $viewModel.errorMessage) { error in - Alert(title: Text("Error"), message: Text(error.message), dismissButton: .default(Text("OK"))) - } - } - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/AppError.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/AppError.swift new file mode 100644 index 0000000..0c0bceb --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/AppError.swift @@ -0,0 +1,6 @@ +import Foundation + +struct AppError: Identifiable { + let id = UUID() + let message: String +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/AuthStatus.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/AuthStatus.swift new file mode 100644 index 0000000..aa361ee --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/AuthStatus.swift @@ -0,0 +1,5 @@ +import Foundation + +struct AuthStatus: Codable { + let authRequired: Bool +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/Constants.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/Constants.swift new file mode 100644 index 0000000..37493eb --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/Constants.swift @@ -0,0 +1,2 @@ +import Foundation +// No global constants needed currently. diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/LoginResponse.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/LoginResponse.swift new file mode 100644 index 0000000..132579e --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Models/LoginResponse.swift @@ -0,0 +1,6 @@ +import Foundation + +struct LoginResponse: Codable { + let message: String + let token: String +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/MyDormDoorApp.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/MyDormDoorApp.swift new file mode 100644 index 0000000..639c07d --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/MyDormDoorApp.swift @@ -0,0 +1,23 @@ +import SwiftUI + +@main +struct MyDormDoorApp: App { + @StateObject var doorViewModel = DoorControlViewModel() + @StateObject var loginViewModel = LoginViewModel() + @State private var pendingShortcutAction: ShortcutActionType? = nil + + var body: some Scene { + WindowGroup { + ContentView(pendingShortcutAction: $pendingShortcutAction) + .environmentObject(doorViewModel) + .environmentObject(loginViewModel) + .onAppear { + // If there was a shortcut action requested at launch, capture it now. + if let action = SceneDelegate.requestedShortcutAction { + pendingShortcutAction = action + SceneDelegate.requestedShortcutAction = nil + } + } + } + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/NetworkManager.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/NetworkManager.swift deleted file mode 100644 index b1d54cc..0000000 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/NetworkManager.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Foundation - -enum NetworkError: Error { - case invalidURL - case invalidResponse - case decodingError - case other(Error) -} - -class NetworkManager { - static let shared = NetworkManager() - - private let baseURL: String = { - // Ensure the baseURL does not contain any trailing whitespace or newlines - let url = "http://192.168.1.100:8080" // Replace with your actual endpoint - return url.trimmingCharacters(in: .whitespacesAndNewlines) - }() - - private init() {} - - func sendCommand(command: String, completion: @escaping (Result) -> Void) { - guard let url = URL(string: "\(baseURL)/command/\(command)") else { - completion(.failure(.invalidURL)) - return // Added return - } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - - // Add any necessary headers here - // request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - URLSession.shared.dataTask(with: request) { data, response, error in - if let error = error { - completion(.failure(.other(error))) - return // Added return - } - - guard let httpResponse = response as? HTTPURLResponse, - 200..<300 ~= httpResponse.statusCode else { - completion(.failure(.invalidResponse)) - return // Added return - } - - completion(.success(())) - }.resume() - } - - func fetchDoorStatus(completion: @escaping (Result) -> Void) { - guard let url = URL(string: "\(baseURL)/status") else { - completion(.failure(.invalidURL)) - return // Added return - } - - let request = URLRequest(url: url) - - URLSession.shared.dataTask(with: request) { data, response, error in - if let error = error { - completion(.failure(.other(error))) - return // Added return - } - - guard let data = data else { - completion(.failure(.invalidResponse)) - return // Added return - } - - do { - let status = try JSONDecoder().decode(DoorStatus.self, from: data) - completion(.success(status)) - } catch { - completion(.failure(.decodingError)) - } - }.resume() - } -} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/SceneDelegate.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/SceneDelegate.swift new file mode 100644 index 0000000..add61f7 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/SceneDelegate.swift @@ -0,0 +1,47 @@ +import UIKit + +enum ShortcutActionType { + case open3sec, open, close, status +} + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + // Store requested action here + static var requestedShortcutAction: ShortcutActionType? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + if let shortcutItem = connectionOptions.shortcutItem { + handleShortcutItem(shortcutItem) + } + guard let _ = (scene as? UIWindowScene) else { return } + } + + func windowScene(_ windowScene: UIWindowScene, + performActionFor shortcutItem: UIApplicationShortcutItem, + completionHandler: @escaping (Bool) -> Void) { + handleShortcutItem(shortcutItem) + completionHandler(true) + } + + private func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem) { + switch shortcutItem.type { + case "com.yourcompany.doorapp.open3sec": + SceneDelegate.requestedShortcutAction = .open3sec + case "com.yourcompany.doorapp.open": + SceneDelegate.requestedShortcutAction = .open + case "com.yourcompany.doorapp.close": + SceneDelegate.requestedShortcutAction = .close + case "com.yourcompany.doorapp.status": + SceneDelegate.requestedShortcutAction = .status + default: + break + } + } + + func sceneDidDisconnect(_ scene: UIScene) {} + func sceneDidBecomeActive(_ scene: UIScene) {} + func sceneWillResignActive(_ scene: UIScene) {} + func sceneWillEnterForeground(_ scene: UIScene) {} + func sceneDidEnterBackground(_ scene: UIScene) {} +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/APIConfig.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/APIConfig.swift new file mode 100644 index 0000000..b06debf --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/APIConfig.swift @@ -0,0 +1,7 @@ +import Foundation + +struct APIConfig { + // Set your server base URL here + static let baseURL = "https://door.ammaar.xyz" + // Adjust this to your actual endpoint if needed. +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/NetworkManager.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/NetworkManager.swift new file mode 100644 index 0000000..ac6c890 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/NetworkManager.swift @@ -0,0 +1,219 @@ +import Foundation + +enum NetworkError: Error { + case invalidURL + case invalidResponse + case decodingError + case unauthorized + case other(Error) + + var localizedDescription: String { + switch self { + case .invalidURL: + return "Invalid URL." + case .invalidResponse: + return "Invalid or unexpected response from the server." + case .decodingError: + return "Failed to decode the data." + case .unauthorized: + return "Unauthorized. Please check your credentials." + case .other(let error): + return error.localizedDescription + } + } +} + +class NetworkManager { + static let shared = NetworkManager() + + private init() {} + + private func getAuthToken() -> String? { + SecureStorage.shared.getAuthToken() + } + + func checkAuthStatus(completion: @escaping (Result) -> Void) { + guard let url = URL(string: "\(APIConfig.baseURL)/auth-status") else { + completion(.failure(.invalidURL)) + return + } + + let request = URLRequest(url: url) + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + completion(.failure(.other(error))) + return + } + + guard let data = data, + let httpResponse = response as? HTTPURLResponse else { + completion(.failure(.invalidResponse)) + return + } + + guard 200..<300 ~= httpResponse.statusCode else { + completion(.failure(.invalidResponse)) + return + } + + do { + let authStatus = try JSONDecoder().decode(AuthStatus.self, from: data) + completion(.success(authStatus)) + } catch { + completion(.failure(.decodingError)) + } + + }.resume() + } + + func login(password: String, completion: @escaping (Result) -> Void) { + guard let url = URL(string: "\(APIConfig.baseURL)/login") else { + completion(.failure(.invalidURL)) + return + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + let body = ["password": password] + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + do { + let bodyData = try JSONSerialization.data(withJSONObject: body, options: []) + request.httpBody = bodyData + } catch { + completion(.failure(.other(error))) + return + } + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + completion(.failure(.other(error))) + return + } + + guard let data = data, + let httpResponse = response as? HTTPURLResponse else { + completion(.failure(.invalidResponse)) + return + } + + guard 200..<300 ~= httpResponse.statusCode else { + if httpResponse.statusCode == 401 { + completion(.failure(.unauthorized)) + } else { + completion(.failure(.invalidResponse)) + } + return + } + + do { + let result = try JSONDecoder().decode(LoginResponse.self, from: data) + completion(.success(result.token)) + } catch { + completion(.failure(.decodingError)) + } + + }.resume() + } + + func sendCommand(command: String, completion: @escaping (Result) -> Void) { + guard let url = URL(string: "\(APIConfig.baseURL)/command") else { + completion(.failure(.invalidURL)) + return + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + let body = ["command": command] + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + if let token = getAuthToken() { + request.setValue(token, forHTTPHeaderField: "Authorization") + } + + do { + let bodyData = try JSONSerialization.data(withJSONObject: body, options: []) + request.httpBody = bodyData + } catch { + completion(.failure(.other(error))) + return + } + + URLSession.shared.dataTask(with: request) { _, response, error in + if let error = error { + completion(.failure(.other(error))) + return + } + + guard let httpResponse = response as? HTTPURLResponse, + 200..<300 ~= httpResponse.statusCode else { + completion(.failure(.invalidResponse)) + return + } + + completion(.success(())) + }.resume() + } + + func fetchDoorStatus(completion: @escaping (Result) -> Void) { + guard let url = URL(string: "\(APIConfig.baseURL)/status") else { + completion(.failure(.invalidURL)) + return + } + + var request = URLRequest(url: url) + if let token = getAuthToken() { + request.setValue(token, forHTTPHeaderField: "Authorization") + } + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + completion(.failure(.other(error))) + return + } + + guard let data = data, + let httpResponse = response as? HTTPURLResponse, + 200..<300 ~= httpResponse.statusCode else { + completion(.failure(.invalidResponse)) + return + } + + do { + let status = try JSONDecoder().decode(DoorStatus.self, from: data) + completion(.success(status)) + } catch { + completion(.failure(.decodingError)) + } + }.resume() + } + + func emergencyClose(completion: @escaping (Result) -> Void) { + guard let url = URL(string: "\(APIConfig.baseURL)/emergency-close") else { + completion(.failure(.invalidURL)) + return + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + if let token = getAuthToken() { + request.setValue(token, forHTTPHeaderField: "Authorization") + } + + URLSession.shared.dataTask(with: request) { _, response, error in + if let error = error { + completion(.failure(.other(error))) + return + } + + guard let httpResponse = response as? HTTPURLResponse, + 200..<300 ~= httpResponse.statusCode else { + completion(.failure(.invalidResponse)) + return + } + + completion(.success(())) + }.resume() + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/SecureStorage.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/SecureStorage.swift new file mode 100644 index 0000000..b785746 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Services/SecureStorage.swift @@ -0,0 +1,20 @@ +import Foundation + +class SecureStorage { + static let shared = SecureStorage() + private let tokenKey = "authTokenKey" + + private init() {} + + func getAuthToken() -> String? { + UserDefaults.standard.string(forKey: tokenKey) + } + + func setAuthToken(_ token: String) { + UserDefaults.standard.set(token, forKey: tokenKey) + } + + func clearAuthToken() { + UserDefaults.standard.removeObject(forKey: tokenKey) + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ViewModels/DoorControlViewModel.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ViewModels/DoorControlViewModel.swift index 2d2f349..ec8e996 100644 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ViewModels/DoorControlViewModel.swift +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ViewModels/DoorControlViewModel.swift @@ -1,57 +1,52 @@ import Foundation import Combine -struct AppError: Identifiable { - let id = UUID() - let message: String -} - class DoorControlViewModel: ObservableObject { @Published var isDoorOpen: Bool = false @Published var isLoading: Bool = false @Published var errorMessage: AppError? - + private var cancellables = Set() - - func toggleDoor() { - let command = isDoorOpen ? "close" : "open" + + func fetchStatus() { isLoading = true - NetworkManager.shared.sendCommand(command: command) { [weak self] result in + NetworkManager.shared.fetchDoorStatus { [weak self] result in DispatchQueue.main.async { self?.isLoading = false switch result { - case .success(_): - self?.isDoorOpen.toggle() + case .success(let status): + self?.isDoorOpen = status.doorOpen case .failure(let error): self?.errorMessage = AppError(message: error.localizedDescription) } } } } - - func emergencyClose() { + + func toggleDoor(open: Bool) { isLoading = true - NetworkManager.shared.sendCommand(command: "emergency-close") { [weak self] result in + let command = open ? "open" : "close" + NetworkManager.shared.sendCommand(command: command) { [weak self] result in DispatchQueue.main.async { self?.isLoading = false switch result { - case .success(_): - self?.isDoorOpen = false + case .success(): + self?.isDoorOpen = open case .failure(let error): self?.errorMessage = AppError(message: error.localizedDescription) } } } } - - func fetchStatus() { + + func emergencyClose() { isLoading = true - NetworkManager.shared.fetchDoorStatus { [weak self] result in + NetworkManager.shared.emergencyClose { [weak self] result in DispatchQueue.main.async { self?.isLoading = false switch result { - case .success(let status): - self?.isDoorOpen = status.doorOpen + case .success(): + self?.isDoorOpen = false case .failure(let error): self?.errorMessage = AppError(message: error.localizedDescription) } diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ViewModels/LoginViewModel.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ViewModels/LoginViewModel.swift new file mode 100644 index 0000000..1b8a09a --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/ViewModels/LoginViewModel.swift @@ -0,0 +1,60 @@ +import Foundation +import Combine + +class LoginViewModel: ObservableObject { + @Published var password: String = "" + @Published var isAuthenticated: Bool = false + @Published var authRequired: Bool = false + @Published var isCheckingAuthStatus: Bool = true + @Published var isLoading: Bool = false + @Published var errorMessage: AppError? + + func checkAuthStatus() { + isCheckingAuthStatus = true + NetworkManager.shared.checkAuthStatus { [weak self] result in + DispatchQueue.main.async { + self?.isCheckingAuthStatus = false + switch result { + case .success(let authStatus): + self?.authRequired = authStatus.authRequired + if !authStatus.authRequired { + self?.isAuthenticated = true + } else { + if let _ = SecureStorage.shared.getAuthToken() { + self?.isAuthenticated = true + } + } + case .failure(let error): + self?.errorMessage = AppError(message: error.localizedDescription) + } + } + } + } + + func login() { + guard !password.isEmpty else { + errorMessage = AppError(message: "Password cannot be empty.") + return + } + + isLoading = true + NetworkManager.shared.login(password: password) { [weak self] result in + DispatchQueue.main.async { + self?.isLoading = false + switch result { + case .success(let token): + SecureStorage.shared.setAuthToken(token) + self?.isAuthenticated = true + self?.password = "" + case .failure(let error): + self?.errorMessage = AppError(message: error.localizedDescription) + } + } + } + } + + func logout() { + SecureStorage.shared.clearAuthToken() + self.isAuthenticated = false + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/ContentView.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/ContentView.swift new file mode 100644 index 0000000..0269b43 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/ContentView.swift @@ -0,0 +1,325 @@ +import SwiftUI + +struct ContentView: View { + @EnvironmentObject var viewModel: DoorControlViewModel + @EnvironmentObject var loginViewModel: LoginViewModel + + @State private var animateShimmer = false + @State private var showingLaunchMessage: Bool = false + @State private var launchMessage: String = "" + + @Binding var pendingShortcutAction: ShortcutActionType? + + var body: some View { + ZStack { + NetworkBackgroundView() + .ignoresSafeArea() + + ScrollView(showsIndicators: false) { + VStack(spacing: 30) { + if loginViewModel.isCheckingAuthStatus { + LoadingView(message: "Checking Authorization...") + .padding(.top, 50) + } else { + if loginViewModel.authRequired && !loginViewModel.isAuthenticated { + loginFormSection + } else { + doorControlSection + } + } + + aboutSection + connectSection + + Spacer(minLength: 50) + } + .padding(.top, 50) + .padding(.horizontal) + } + } + .onAppear { + loginViewModel.checkAuthStatus() + animateShimmer = true + } + .onChange(of: loginViewModel.isAuthenticated) { isAuth in + if isAuth { + tryPerformShortcutAction() + } + } + .alert(isPresented: $showingLaunchMessage) { + Alert(title: Text("Shortcut Action"), message: Text(launchMessage), dismissButton: .default(Text("OK"))) + } + .alert(item: $viewModel.errorMessage) { error in + Alert(title: Text("Error"), message: Text(error.message), dismissButton: .default(Text("OK"))) + } + .alert(item: $loginViewModel.errorMessage) { error in + Alert(title: Text("Error"), message: Text(error.message), dismissButton: .default(Text("OK"))) + } + } + + private func tryPerformShortcutAction() { + guard let action = pendingShortcutAction else { return } + pendingShortcutAction = nil + + if loginViewModel.authRequired && !loginViewModel.isAuthenticated { + launchMessage = "Please log in to perform this action." + showingLaunchMessage = true + return + } + + performShortcutAction(action) + } + + private func performShortcutAction(_ action: ShortcutActionType) { + switch action { + case .open3sec: + NetworkManager.shared.sendCommand(command: "open") { result in + DispatchQueue.main.async { + switch result { + case .success(): + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + NetworkManager.shared.sendCommand(command: "close") { res2 in + DispatchQueue.main.async { + switch res2 { + case .success(): + self.launchMessage = "Door opened, waited 3s, then closed." + case .failure(let err): + self.launchMessage = "Opened door, but failed to close after 3s: \(err.localizedDescription)" + } + self.showingLaunchMessage = true + self.viewModel.fetchStatus() + } + } + } + + case .failure(let error): + self.launchMessage = "Failed to open door (3s): \(error.localizedDescription)" + self.showingLaunchMessage = true + self.viewModel.fetchStatus() + } + } + } + + case .open: + NetworkManager.shared.sendCommand(command: "open") { result in + DispatchQueue.main.async { + switch result { + case .success(): + self.launchMessage = "Door opened via shortcut." + case .failure(let error): + self.launchMessage = "Failed to open door: \(error.localizedDescription)" + } + self.showingLaunchMessage = true + self.viewModel.fetchStatus() + } + } + + case .close: + NetworkManager.shared.sendCommand(command: "close") { result in + DispatchQueue.main.async { + switch result { + case .success(): + self.launchMessage = "Door closed via shortcut." + case .failure(let error): + self.launchMessage = "Failed to close door: \(error.localizedDescription)" + } + self.showingLaunchMessage = true + self.viewModel.fetchStatus() + } + } + + case .status: + NetworkManager.shared.fetchDoorStatus { result in + DispatchQueue.main.async { + switch result { + case .success(let status): + self.launchMessage = "Door is \(status.doorOpen ? "Open" : "Closed")" + case .failure(let error): + self.launchMessage = "Error getting status: \(error.localizedDescription)" + } + self.showingLaunchMessage = true + self.viewModel.fetchStatus() + } + } + } + } + + private var loginFormSection: some View { + VStack(alignment: .center, spacing: 20) { + Text("Please Log In") + .font(.largeTitle) + .fontWeight(.semibold) + .gradientText() + .padding(.bottom, 10) + .frame(maxWidth: .infinity, alignment: .center) + + SecureField("Enter Password", text: $loginViewModel.password) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .foregroundColor(AppTheme.text) + .padding(.horizontal) + .accentColor(AppTheme.primary) + + if loginViewModel.isLoading { + ProgressView("Logging In...") + .tint(AppTheme.primary) + } else { + Button(action: { + loginViewModel.login() + }) { + Text("Login") + .font(.headline) + .foregroundColor(AppTheme.background) + .padding() + .frame(maxWidth: .infinity) + .background(AppTheme.highlightGradient) + .cornerRadius(10) + .shadow(color: AppTheme.primary.opacity(0.4), radius: 10) + } + .padding(.horizontal) + } + + Text("Login to control the door. If you don't have the password, you can still view the info below.") + .font(.footnote) + .foregroundColor(AppTheme.textSecondary) + .multilineTextAlignment(.center) + } + .padding() + .background(AppTheme.cardBg) + .cornerRadius(15) + .overlay(RoundedRectangle(cornerRadius: 15).stroke(AppTheme.border, lineWidth: 1)) + .shadow(color: .black.opacity(0.5), radius: 8) + } + + private var doorControlSection: some View { + VStack(spacing: 20) { + shimmerTitle("Door Controls") + + StatusView(isDoorOpen: viewModel.isDoorOpen) + + DoorControlView(viewModel: viewModel) + + if viewModel.isLoading { + ProgressView("Processing...") + .tint(AppTheme.primary) + .padding() + } + + if loginViewModel.authRequired && loginViewModel.isAuthenticated { + Button("Logout") { + loginViewModel.logout() + } + .font(.headline) + .foregroundColor(AppTheme.background) + .padding() + .background(AppTheme.highlightGradient) + .cornerRadius(10) + .shadow(color: AppTheme.primary.opacity(0.4), radius: 10) + .padding(.bottom, 20) + } + } + .padding() + .background(AppTheme.cardBg) + .cornerRadius(15) + .overlay(RoundedRectangle(cornerRadius: 15).stroke(AppTheme.border, lineWidth: 1)) + .shadow(color: .black.opacity(0.5), radius: 8) + .onAppear { + if loginViewModel.isAuthenticated { + viewModel.fetchStatus() + } + } + } + + private var aboutSection: some View { + VStack(alignment: .leading, spacing: 20) { + shimmerTitle("About This Project") + + Text("This is a personal project I worked on over the summer. The toggle above really does open or close my door. It sends a command to my proxy server which routes that command to the Arduino IoT Cloud, relaying the command to the Arduino. That Arduino is connected to a motor driver which then spins a DC motor, reeling a fishing line knotted around my door handle, pulling it down.") + .foregroundColor(AppTheme.text) + .font(.body) + .fixedSize(horizontal: false, vertical: true) + + Text("All parts are open source on GitHub, so feel free to build your own!") + .font(.footnote) + .foregroundColor(AppTheme.textSecondary) + .multilineTextAlignment(.center) + + Divider().background(AppTheme.border) + } + .padding() + .background(AppTheme.cardBg) + .cornerRadius(15) + .overlay(RoundedRectangle(cornerRadius: 15).stroke(AppTheme.border, lineWidth: 1)) + .shadow(color: .black.opacity(0.5), radius: 8) + } + + private var connectSection: some View { + VStack(alignment: .center, spacing: 20) { + shimmerTitle("Connect with Me") + + VStack(alignment: .center, spacing: 15) { + externalLinkButton(icon: "camera.fill", text: "Coding Portfolio / Personal Site", url: "https://ammaaralam.com") + externalLinkButton(icon: "chevron.left.forwardslash.chevron.right", text: "GitHub", url: "https://github.com/Ammaar-Alam/doorUnlocker") + externalLinkButton(icon: "link", text: "LinkedIn", url: "https://www.linkedin.com/in/Ammaar-Alam") + externalLinkButton(icon: "camera.on.rectangle", text: "Photography Portfolio", url: "https://ammaar.xyz") + } + } + .padding() + .background(AppTheme.cardBg) + .cornerRadius(15) + .overlay(RoundedRectangle(cornerRadius: 15).stroke(AppTheme.border, lineWidth: 1)) + .shadow(color: .black.opacity(0.5), radius: 8) + } + + private func shimmerTitle(_ text: String) -> some View { + ZStack { + Text(text) + .font(.title2) + .fontWeight(.semibold) + .foregroundColor(AppTheme.text) + + LinearGradient( + gradient: Gradient(colors: [AppTheme.gradientStart.opacity(0), AppTheme.gradientStart, AppTheme.gradientEnd, AppTheme.gradientStart.opacity(0)]), + startPoint: .leading, + endPoint: .trailing + ) + .frame(width: 200, height: 20) + .offset(x: animateShimmer ? 200 : -200) + .mask( + Text(text) + .font(.title2) + .fontWeight(.semibold) + ) + } + .frame(maxWidth: .infinity) + .animation(.linear(duration: 2.0).repeatForever(autoreverses: true), value: animateShimmer) + } + + private func externalLinkButton(icon: String, text: String, url: String) -> some View { + @State var isPressed = false + return Button(action: { + if let linkURL = URL(string: url) { + UIApplication.shared.open(linkURL, options: [:], completionHandler: nil) + } + }) { + HStack(spacing: 8) { + Image(systemName: icon) + .foregroundColor(isPressed ? AppTheme.background : AppTheme.primary) + Text(text) + .font(.headline) + .foregroundColor(isPressed ? AppTheme.background : AppTheme.primary) + } + .padding(8) + .background(isPressed ? AppTheme.primary : AppTheme.cardBg) + .cornerRadius(8) + .overlay(RoundedRectangle(cornerRadius: 8).stroke(AppTheme.border, lineWidth: 1)) + .shadow(color: AppTheme.primary.opacity(0.3), radius: 5) + .scaleEffect(isPressed ? 0.95 : 1.0) + .animation(.easeInOut(duration: 0.2), value: isPressed) + } + .simultaneousGesture( + DragGesture(minimumDistance: 0) + .onChanged { _ in isPressed = true } + .onEnded { _ in isPressed = false } + ) + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/DoorControlView.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/DoorControlView.swift index a8b83a9..dbb644e 100644 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/DoorControlView.swift +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/DoorControlView.swift @@ -8,19 +8,22 @@ struct DoorControlView: View { Toggle(isOn: $viewModel.isDoorOpen) { Text(viewModel.isDoorOpen ? "Close Door" : "Open Door") .font(.headline) + .foregroundColor(AppTheme.text) } - .toggleStyle(SwitchToggleStyle(tint: .blue)) + // Color toggle: red if open, green if closed + .toggleStyle(SwitchToggleStyle(tint: viewModel.isDoorOpen ? .red : .green)) .disabled(viewModel.isLoading) - .onChange(of: viewModel.isDoorOpen) { _ in - viewModel.toggleDoor() + .onChange(of: viewModel.isDoorOpen) { newValue in + viewModel.toggleDoor(open: newValue) } EmergencyButton(action: viewModel.emergencyClose) .disabled(viewModel.isLoading) } .padding() - .background(Color(.systemBackground)) // Uses SwiftUI's systemBackground + .background(AppTheme.cardBg) .cornerRadius(15) - .shadow(radius: 5) + .overlay(RoundedRectangle(cornerRadius: 15).stroke(AppTheme.border, lineWidth: 1)) + .shadow(color: .black.opacity(0.5), radius: 8) } -} \ No newline at end of file +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/EmergencyButton.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/EmergencyButton.swift index 64b9c06..cb62855 100644 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/EmergencyButton.swift +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/EmergencyButton.swift @@ -5,13 +5,14 @@ struct EmergencyButton: View { var body: some View { Button(action: action) { - Text("Emergency Close / Untangle") + Text("Force Close / Untangle") .font(.headline) - .foregroundColor(.white) + .foregroundColor(AppTheme.background) .padding() .frame(maxWidth: .infinity) - .background(Color.red) + .background(AppTheme.primary) .cornerRadius(10) + .shadow(color: AppTheme.primary.opacity(0.4), radius: 10) } } -} \ No newline at end of file +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/LoadingView.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/LoadingView.swift new file mode 100644 index 0000000..593fa54 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/LoadingView.swift @@ -0,0 +1,16 @@ +import SwiftUI + +struct LoadingView: View { + let message: String + + var body: some View { + VStack(spacing: 20) { + ProgressView() + .tint(AppTheme.primary) + Text(message) + .font(.headline) + .foregroundColor(AppTheme.text) + } + .padding() + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/NetworkBackgroundView.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/NetworkBackgroundView.swift new file mode 100644 index 0000000..b46590f --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/NetworkBackgroundView.swift @@ -0,0 +1,119 @@ +import SwiftUI + +struct NetworkBackgroundView: View { + @State private var nodes = [Node]() + @State private var lastUpdateTime: Date = Date() + @State private var timerCancellable: Any? + + let updateInterval = 1.0/30.0 + let nodeCount = 15 + let nodeMaxDistance: CGFloat = 170 + let nodeRadius: CGFloat = 6 + + var body: some View { + Canvas { context, size in + context.fill(Path(CGRect(origin: .zero, size: size)), with: .color(AppTheme.background)) + + drawConnections(nodes: nodes, in: &context, size: size) + drawNodes(nodes: nodes, in: &context) + } + .onAppear { + if nodes.isEmpty { + nodes = (0.. size.width + 50 { position.x = -50 } + if position.y < -50 { position.y = size.height + 50 } + if position.y > size.height + 50 { position.y = -50 } + + vx += CGFloat.random(in: -0.003...0.003) + vy += CGFloat.random(in: -0.003...0.003) + let maxSpeed: CGFloat = 0.3 + let speedMag = sqrt(vx*vx + vy*vy) + if speedMag > maxSpeed { + vx = (vx / speedMag) * maxSpeed + vy = (vy / speedMag) * maxSpeed + } + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/StatusView.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/StatusView.swift index 08b9347..12281c0 100644 --- a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/StatusView.swift +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/StatusView.swift @@ -4,7 +4,7 @@ struct StatusView: View { let isDoorOpen: Bool var body: some View { - HStack { + HStack(spacing: 20) { Image(systemName: isDoorOpen ? "door.left.hand.open" : "door.left.hand.closed") .font(.system(size: 50)) .foregroundColor(isDoorOpen ? .red : .green) @@ -12,10 +12,12 @@ struct StatusView: View { Text(isDoorOpen ? "Door is Open" : "Door is Closed") .font(.title2) .fontWeight(.semibold) + .foregroundColor(AppTheme.text) } .padding() - .background(Color(.systemBackground)) // Uses SwiftUI's systemBackground + .background(AppTheme.cardBg) .cornerRadius(15) - .shadow(radius: 5) + .overlay(RoundedRectangle(cornerRadius: 15).stroke(AppTheme.border, lineWidth: 1)) + .shadow(color: .black.opacity(0.5), radius: 8) } -} \ No newline at end of file +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/Theme.swift b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/Theme.swift new file mode 100644 index 0000000..c70a8a9 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar's Dorm Controls/Views/Theme.swift @@ -0,0 +1,49 @@ +import SwiftUI + +struct AppTheme { + // Core colors from your portfolio scheme + static let background = Color(hex: "#0a0a0a") + static let cardBg = Color(hex: "#18181b") + static let text = Color(hex: "#ffffff") + static let textSecondary = Color(hex: "#a1a1aa") + static let border = Color(hex: "#27272a") + static let primary = Color(hex: "#8ffcff") // primary accent + static let gradientStart = Color(hex: "#8ffcff") + static let gradientEnd = Color(hex: "#4dc6ff") + + static let highlightGradient = LinearGradient( + gradient: Gradient(colors: [gradientStart, gradientEnd]), + startPoint: .topLeading, endPoint: .bottomTrailing + ) +} + +// Convenience init for Color from hex +extension Color { + init(hex: String) { + let trimmed = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + let hexString = trimmed.hasPrefix("#") ? String(trimmed.dropFirst()) : trimmed + var rgb: UInt64 = 0 + Scanner(string: hexString).scanHexInt64(&rgb) + + let r = Double((rgb & 0xFF0000) >> 16) / 255.0 + let g = Double((rgb & 0x00FF00) >> 8) / 255.0 + let b = Double(rgb & 0x0000FF) / 255.0 + + self.init(red: r, green: g, blue: b) + } +} + +// A modifier for gradient text +struct GradientText: ViewModifier { + func body(content: Content) -> some View { + content + .overlay(AppTheme.highlightGradient) + .mask(content) + } +} + +extension View { + func gradientText() -> some View { + self.modifier(GradientText()) + } +} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm ControlsTests/Ammaar_s_Dorm_ControlsTests.swift b/Ammaar's Dorm Controls/Ammaar's Dorm ControlsTests/Ammaar_s_Dorm_ControlsTests.swift deleted file mode 100644 index 73aa383..0000000 --- a/Ammaar's Dorm Controls/Ammaar's Dorm ControlsTests/Ammaar_s_Dorm_ControlsTests.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Ammaar_s_Dorm_ControlsTests.swift -// Ammaar's Dorm ControlsTests -// -// Created by Ammaar Alam on 9/19/24. -// - -import Testing - -struct Ammaar_s_Dorm_ControlsTests { - - @Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. - } - -} diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm ControlsUITests/Ammaar_s_Dorm_ControlsUITests.swift b/Ammaar's Dorm Controls/Ammaar's Dorm ControlsUITests/Ammaar_s_Dorm_ControlsUITests.swift deleted file mode 100644 index e69de29..0000000 diff --git a/Ammaar's Dorm Controls/Ammaar's Dorm ControlsUITests/Ammaar_s_Dorm_ControlsUITestsLaunchTests.swift b/Ammaar's Dorm Controls/Ammaar's Dorm ControlsUITests/Ammaar_s_Dorm_ControlsUITestsLaunchTests.swift deleted file mode 100644 index bc473fd..0000000 --- a/Ammaar's Dorm Controls/Ammaar's Dorm ControlsUITests/Ammaar_s_Dorm_ControlsUITestsLaunchTests.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Ammaar_s_Dorm_ControlsUITestsLaunchTests.swift -// Ammaar's Dorm ControlsUITests -// -// Created by Ammaar Alam on 9/19/24. -// - -import XCTest - -final class Ammaar_s_Dorm_ControlsUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - @MainActor - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} diff --git a/Ammaar's Dorm Controls/Ammaar-s-Dorm-Controls-Info.plist b/Ammaar's Dorm Controls/Ammaar-s-Dorm-Controls-Info.plist new file mode 100644 index 0000000..0a3df35 --- /dev/null +++ b/Ammaar's Dorm Controls/Ammaar-s-Dorm-Controls-Info.plist @@ -0,0 +1,61 @@ + + + + + NSSupportsLiveActivities + + UIBackgroundModes + + remote-notification + + UIApplicationShortcutItems + + + UIApplicationShortcutItemIconType + Compose + UIApplicationShortcutItemTitle + Open Door (3 Seconds) + UIApplicationShortcutItemType + com.yourcompany.doorapp.open3sec + + + UIApplicationShortcutItemIconType + Play + UIApplicationShortcutItemTitle + Open Door + UIApplicationShortcutItemType + com.yourcompany.doorapp.open + + + UIApplicationShortcutItemIconType + Pause + UIApplicationShortcutItemTitle + Close Door + UIApplicationShortcutItemType + com.yourcompany.doorapp.close + + + UIApplicationShortcutItemIconType + Search + UIApplicationShortcutItemTitle + Door Status + UIApplicationShortcutItemType + com.yourcompany.doorapp.status + + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + diff --git a/Ammaar's Dorm Controls/Door Control Widget/DoorWidget.swift b/Ammaar's Dorm Controls/Door Control Widget/DoorWidget.swift new file mode 100644 index 0000000..431af81 --- /dev/null +++ b/Ammaar's Dorm Controls/Door Control Widget/DoorWidget.swift @@ -0,0 +1,156 @@ +import WidgetKit +import SwiftUI +import AppIntents +import Foundation + +@available(iOS 18.0, *) +struct DoorStatusEntry: TimelineEntry { + let date: Date + let isDoorOpen: Bool + let configuration: ConfigurationAppIntent +} + +@available(iOS 18.0, *) +struct ConfigurationAppIntent: AppIntent { + static var title: LocalizedStringResource = "Configuration" + static var description = IntentDescription("Widget Configuration") + + init() {} +} + +@available(iOS 18.0, *) +struct DoorStatusProvider: AppIntentTimelineProvider { + typealias Entry = DoorStatusEntry + typealias Intent = ConfigurationAppIntent + + func placeholder(in context: Context) -> DoorStatusEntry { + DoorStatusEntry(date: Date(), isDoorOpen: false, configuration: ConfigurationAppIntent()) + } + + func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> DoorStatusEntry { + DoorStatusEntry(date: Date(), isDoorOpen: false, configuration: configuration) + } + + func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { + let currentStatus = await fetchCurrentDoorStatus() + let entry = DoorStatusEntry(date: Date(), isDoorOpen: currentStatus, configuration: configuration) + return Timeline(entries: [entry], policy: .after(Date().addingTimeInterval(30))) + } + + private func fetchCurrentDoorStatus() async -> Bool { + guard let url = URL(string: "\(APIConfig.baseURL)/status") else { + return false + } + + do { + let (data, response) = try await URLSession.shared.data(from: url) + guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else { + return false + } + let status = try JSONDecoder().decode(DoorStatus.self, from: data) + return status.doorOpen + } catch { + return false + } + } +} + +@available(iOS 18.0, *) +struct DoorWidgetEntryView: View { + var entry: DoorStatusEntry + @Environment(\.widgetFamily) var family + + var body: some View { + ZStack { + AppTheme.background + .ignoresSafeArea() + + VStack(spacing: 8) { + HStack { + Text("Dorm Door") + .font(.headline) + .foregroundColor(AppTheme.text) + Spacer() + } + + HStack { + Image(systemName: entry.isDoorOpen ? "door.left.hand.open" : "door.left.hand.closed") + .font(.system(size: family == .systemSmall ? 24 : 30)) + .foregroundColor(entry.isDoorOpen ? .red : .green) + Text(entry.isDoorOpen ? "Open" : "Closed") + .font(family == .systemSmall ? .title3 : .title2) + .fontWeight(.semibold) + .foregroundColor(AppTheme.text) + } + + if family != .systemSmall { + HStack(spacing: 12) { + Button(intent: OpenDoorIntent()) { + Text("Open") + .font(.subheadline) + .foregroundColor(AppTheme.text) + .padding(6) + .background(AppTheme.cardBg) + .cornerRadius(6) + } + .buttonStyle(.plain) + + Button(intent: CloseDoorIntent()) { + Text("Close") + .font(.subheadline) + .foregroundColor(AppTheme.text) + .padding(6) + .background(AppTheme.cardBg) + .cornerRadius(6) + } + .buttonStyle(.plain) + } + } + } + .padding() + } + .containerBackground(AppTheme.background, for: .widget) + } +} + +@available(iOS 18.0, *) +struct DoorWidget: Widget { + private let supportedFamilies: [WidgetFamily] = [ + .systemSmall, + .systemMedium, + .accessoryCircular, + .accessoryRectangular + ] + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: "DoorWidget", + intent: ConfigurationAppIntent.self, + provider: DoorStatusProvider()) { entry in + DoorWidgetEntryView(entry: entry) + } + .configurationDisplayName("Dorm Door") + .description("View and control your dorm door from your home screen.") + .supportedFamilies(supportedFamilies) + .contentMarginsDisabled() + } +} + +@available(iOS 18.0, *) +struct DoorWidgetBundle: WidgetBundle { + var body: some Widget { + DoorWidget() + } +} + +// Preview provider for SwiftUI canvas +@available(iOS 18.0, *) +struct DoorWidget_Previews: PreviewProvider { + static var previews: some View { + DoorWidgetEntryView(entry: DoorStatusEntry( + date: Date(), + isDoorOpen: false, + configuration: ConfigurationAppIntent() + )) + .previewContext(WidgetPreviewContext(family: .systemMedium)) + } +} diff --git a/Ammaar's Dorm Controls/Door Control Widget/Info.plist b/Ammaar's Dorm Controls/Door Control Widget/Info.plist new file mode 100644 index 0000000..5304a57 --- /dev/null +++ b/Ammaar's Dorm Controls/Door Control Widget/Info.plist @@ -0,0 +1,17 @@ + + + + + CFBundleDisplayName + Door Widget + CFBundleIdentifier + com.yourcompany.AmmaarsDormControls.DoorWidget + CFBundleName + Door Widget + CFBundlePackageType + APPL + MinimumOSVersion + 16.0 + + diff --git a/Ammaar's Dorm Controls/Info.plist b/Ammaar's Dorm Controls/Info.plist new file mode 100644 index 0000000..3240a53 --- /dev/null +++ b/Ammaar's Dorm Controls/Info.plist @@ -0,0 +1,72 @@ + + + + + NSSupportsLiveActivities + + UIBackgroundModes + + remote-notification + + UIApplicationShortcutItems + + + UIApplicationShortcutItemIconType + Compose + UIApplicationShortcutItemTitle + Open Door (3 Seconds) + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).open3sec + + + UIApplicationShortcutItemIconType + Play + UIApplicationShortcutItemTitle + Open Door + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).open + + + UIApplicationShortcutItemIconType + Pause + UIApplicationShortcutItemTitle + Close Door + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).close + + + UIApplicationShortcutItemIconType + Search + UIApplicationShortcutItemTitle + Door Status + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).status + + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + NSSiriUsageDescription + This app uses Siri to control your dorm door through voice commands and shortcuts. + NSUserActivityTypes + + OpenDoorIntent + OpenDoorIn3SecIntent + CloseDoorIntent + DoorActionIntent + GetDoorStatusIntent + + +