diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 070daf87..e4203cc8 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -32,8 +32,6 @@ DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; }; DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582928585C32009B0E59 /* RangeTestConfig.swift */; }; DD41A61529AB0035003C5A37 /* NodeWeatherForecast.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */; }; - DD41A61D29AE7E8F003C5A37 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61C29AE7E8E003C5A37 /* WidgetKit.framework */; }; - DD41A61F29AE7E8F003C5A37 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61E29AE7E8F003C5A37 /* SwiftUI.framework */; }; DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */; }; DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; }; DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; }; @@ -116,6 +114,16 @@ DDD3BBD5292D763200D609B3 /* MeshtasticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; + DDDE59F529AF163D00490C6C /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61C29AE7E8E003C5A37 /* WidgetKit.framework */; }; + DDDE59F629AF163D00490C6C /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61E29AE7E8F003C5A37 /* SwiftUI.framework */; }; + DDDE59F929AF163D00490C6C /* WidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */; }; + DDDE59FB29AF163D00490C6C /* WidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE59FA29AF163D00490C6C /* WidgetsLiveActivity.swift */; }; + DDDE59FD29AF163D00490C6C /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE59FC29AF163D00490C6C /* Widgets.swift */; }; + DDDE5A0329AF163E00490C6C /* WidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */; }; + DDDE5A1129AFE69700490C6C /* MeshActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */; }; + DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; + DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; @@ -136,8 +144,29 @@ remoteGlobalIDString = DDC2E15326CE248E0042C5E4; remoteInfo = MeshtasticClient; }; + DDDE5A0129AF163E00490C6C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDC2E14C26CE248E0042C5E4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDDE59F329AF163D00490C6C; + remoteInfo = WidgetsExtension; + }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + DDDE5A0829AF163F00490C6C /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + DDDE5A0329AF163E00490C6C /* WidgetsExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; @@ -164,7 +193,6 @@ DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; DD41582928585C32009B0E59 /* RangeTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeTestConfig.swift; sourceTree = ""; }; DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWeatherForecast.swift; sourceTree = ""; }; - DD41A61B29AE7E8E003C5A37 /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; DD41A61C29AE7E8E003C5A37 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; DD41A61E29AE7E8F003C5A37 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalStrengthIndicator.swift; sourceTree = ""; }; @@ -262,6 +290,14 @@ DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; + DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsBundle.swift; sourceTree = ""; }; + DDDE59FA29AF163D00490C6C /* WidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsLiveActivity.swift; sourceTree = ""; }; + DDDE59FC29AF163D00490C6C /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = ""; }; + DDDE5A0029AF163E00490C6C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DDDE5A0429AF163E00490C6C /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = ""; }; + DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = ""; }; + DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; @@ -269,15 +305,6 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - DD41A61829AE7E8E003C5A37 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DD41A61F29AE7E8F003C5A37 /* SwiftUI.framework in Frameworks */, - DD41A61D29AE7E8F003C5A37 /* WidgetKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DDC2E15126CE248E0042C5E4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -301,6 +328,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DDDE59F129AF163D00490C6C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DDDE59F629AF163D00490C6C /* SwiftUI.framework in Frameworks */, + DDDE59F529AF163D00490C6C /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -476,11 +512,13 @@ DDC2E14B26CE248E0042C5E4 = { isa = PBXGroup; children = ( + DDDE5A0429AF163E00490C6C /* WidgetsExtension.entitlements */, DDCDC6CD29481FCC004C1DDA /* Localizable.strings */, DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */, DDC2E15626CE248E0042C5E4 /* Meshtastic */, DDC2E16D26CE248F0042C5E4 /* MeshtasticTests */, DDC2E17826CE248F0042C5E4 /* MeshtasticUITests */, + DDDE59F729AF163D00490C6C /* Widgets */, DDC2E15526CE248E0042C5E4 /* Products */, DD8EDE9226F97A2B00A5A10B /* Frameworks */, ); @@ -493,7 +531,7 @@ DDC2E15426CE248E0042C5E4 /* Meshtastic.app */, DDC2E16A26CE248F0042C5E4 /* MeshtasticTests.xctest */, DDC2E17526CE248F0042C5E4 /* MeshtasticUITests.xctest */, - DD41A61B29AE7E8E003C5A37 /* WidgetsExtension.appex */, + DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */, ); name = Products; sourceTree = ""; @@ -629,26 +667,22 @@ path = Persistence; sourceTree = ""; }; + DDDE59F729AF163D00490C6C /* Widgets */ = { + isa = PBXGroup; + children = ( + DDDE5A1229AFEAB900490C6C /* Assets.xcassets */, + DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */, + DDDE59FA29AF163D00490C6C /* WidgetsLiveActivity.swift */, + DDDE59FC29AF163D00490C6C /* Widgets.swift */, + DDDE5A0029AF163E00490C6C /* Info.plist */, + DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */, + ); + path = Widgets; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - DD41A61A29AE7E8E003C5A37 /* WidgetsExtension */ = { - isa = PBXNativeTarget; - buildConfigurationList = DD41A62A29AE7E91003C5A37 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */; - buildPhases = ( - DD41A61729AE7E8E003C5A37 /* Sources */, - DD41A61829AE7E8E003C5A37 /* Frameworks */, - DD41A61929AE7E8E003C5A37 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = WidgetsExtension; - productName = WidgetsExtension; - productReference = DD41A61B29AE7E8E003C5A37 /* WidgetsExtension.appex */; - productType = "com.apple.product-type.app-extension"; - }; DDC2E15326CE248E0042C5E4 /* Meshtastic */ = { isa = PBXNativeTarget; buildConfigurationList = DDC2E17E26CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "Meshtastic" */; @@ -657,10 +691,12 @@ DDC2E15126CE248E0042C5E4 /* Frameworks */, DDC2E15226CE248E0042C5E4 /* Resources */, BB450974275599CE00509624 /* ShellScript */, + DDDE5A0829AF163F00490C6C /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + DDDE5A0229AF163E00490C6C /* PBXTargetDependency */, ); name = Meshtastic; packageProductDependencies = ( @@ -707,6 +743,23 @@ productReference = DDC2E17526CE248F0042C5E4 /* MeshtasticUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + DDDE59F329AF163D00490C6C /* WidgetsExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = DDDE5A0529AF163F00490C6C /* Build configuration list for PBXNativeTarget "WidgetsExtension" */; + buildPhases = ( + DDDE59F029AF163D00490C6C /* Sources */, + DDDE59F129AF163D00490C6C /* Frameworks */, + DDDE59F229AF163D00490C6C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = WidgetsExtension; + productName = WidgetsExtension; + productReference = DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -716,9 +769,6 @@ LastSwiftUpdateCheck = 1420; LastUpgradeCheck = 1250; TargetAttributes = { - DD41A61A29AE7E8E003C5A37 = { - CreatedOnToolsVersion = 14.2; - }; DDC2E15326CE248E0042C5E4 = { CreatedOnToolsVersion = 12.5.1; LastSwiftMigration = 1340; @@ -732,6 +782,9 @@ CreatedOnToolsVersion = 12.5.1; TestTargetID = DDC2E15326CE248E0042C5E4; }; + DDDE59F329AF163D00490C6C = { + CreatedOnToolsVersion = 14.2; + }; }; }; buildConfigurationList = DDC2E14F26CE248E0042C5E4 /* Build configuration list for PBXProject "Meshtastic" */; @@ -756,25 +809,19 @@ DDC2E15326CE248E0042C5E4 /* Meshtastic */, DDC2E16926CE248F0042C5E4 /* MeshtasticTests */, DDC2E17426CE248F0042C5E4 /* MeshtasticUITests */, - DD41A61A29AE7E8E003C5A37 /* WidgetsExtension */, + DDDE59F329AF163D00490C6C /* WidgetsExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - DD41A61929AE7E8E003C5A37 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; DDC2E15226CE248E0042C5E4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */, DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */, + DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */, DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -793,6 +840,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DDDE59F229AF163D00490C6C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -816,13 +871,6 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - DD41A61729AE7E8E003C5A37 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; DDC2E15026CE248E0042C5E4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -899,6 +947,7 @@ DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, + DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */, DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */, DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */, @@ -949,6 +998,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DDDE59F029AF163D00490C6C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DDDE5A1129AFE69700490C6C /* MeshActivityAttributes.swift in Sources */, + DDDE59FB29AF163D00490C6C /* WidgetsLiveActivity.swift in Sources */, + DDDE59FD29AF163D00490C6C /* Widgets.swift in Sources */, + DDDE59F929AF163D00490C6C /* WidgetsBundle.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -962,6 +1022,11 @@ target = DDC2E15326CE248E0042C5E4 /* Meshtastic */; targetProxy = DDC2E17626CE248F0042C5E4 /* PBXContainerItemProxy */; }; + DDDE5A0229AF163E00490C6C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDDE59F329AF163D00490C6C /* WidgetsExtension */; + targetProxy = DDDE5A0129AF163E00490C6C /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -978,64 +1043,6 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - DD41A62B29AE7E91003C5A37 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = GCH7VS5Y9R; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Widgets/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = Widgets; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - DD41A62C29AE7E91003C5A37 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = GCH7VS5Y9R; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Widgets/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = Widgets; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; DDC2E17C26CE248F0042C5E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1088,7 +1095,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1144,7 +1151,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1159,6 +1166,7 @@ DDC2E17F26CE248F0042C5E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; @@ -1192,6 +1200,7 @@ DDC2E18026CE248F0042C5E4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; @@ -1310,18 +1319,71 @@ }; name = Release; }; + DDDE5A0629AF163F00490C6C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GCH7VS5Y9R; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Widgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Widgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DDDE5A0729AF163F00490C6C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GCH7VS5Y9R; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Widgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Widgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - DD41A62A29AE7E91003C5A37 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DD41A62B29AE7E91003C5A37 /* Debug */, - DD41A62C29AE7E91003C5A37 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; DDC2E14F26CE248E0042C5E4 /* Build configuration list for PBXProject "Meshtastic" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1358,6 +1420,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DDDE5A0529AF163F00490C6C /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DDDE5A0629AF163F00490C6C /* Debug */, + DDDE5A0729AF163F00490C6C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 0f7affb4..7c589b2c 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -10,9 +10,12 @@ import MapKit import CoreData import CoreLocation import CoreBluetooth +#if canImport(ActivityKit) +import ActivityKit +#endif struct Connect: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings @@ -21,177 +24,196 @@ struct Connect: View { @State var isPreferredRadio: Bool = false @State var isUnsetRegion = false @State var invalidFirmwareVersion = false - - var body: some View { + @State var liveActivityStarted = false + var body: some View { + NavigationStack { VStack { List { if bleManager.isSwitchedOn { - Section(header: Text("connected.radio").font(.title)) { - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected { - HStack { - Image(systemName: "antenna.radiowaves.left.and.right") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.green) - .padding(.trailing) - VStack(alignment: .leading) { - if node != nil { - Text(bleManager.connectedPeripheral.longName).font(.title2) - } - Text("ble.name").font(.caption)+Text(": \(bleManager.connectedPeripheral.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") - .font(.caption).foregroundColor(Color.gray) - if node != nil { - Text("firmware.version").font(.caption)+Text(": \(node?.myInfo?.firmwareVersion ?? NSLocalizedString("unknown", comment: "Unknown"))") - .font(.caption).foregroundColor(Color.gray) - } - if bleManager.isSubscribed { - Text("subscribed").font(.caption) - .foregroundColor(.green) - } else { - Text("communicating").font(.caption) - .foregroundColor(.orange) - } - } - Spacer() - VStack(alignment: .center) { - Text("preferred.radio").font(.caption2) - .multilineTextAlignment(.center) - .frame(width: 75) - Toggle("preferred.radio", isOn: $bleManager.preferredPeripheral) - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .labelsHidden() - .onChange(of: bleManager.preferredPeripheral) { value in - if value { - if bleManager.connectedPeripheral != nil { - userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString - userSettings.preferredNodeNum = bleManager.connectedPeripheral!.num - bleManager.preferredPeripheral = true - isPreferredRadio = true - } - } else { - - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { - - userSettings.preferredPeripheralId = "" - userSettings.preferredNodeNum = 0 - bleManager.preferredPeripheral = false - isPreferredRadio = false - } - } - } - } - } - .font(.caption).foregroundColor(Color.gray) - .padding([.top, .bottom]) - .swipeActions { - - Button(role: .destructive) { - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { - bleManager.disconnectPeripheral(reconnect: false) - isPreferredRadio = false - } - } label: { - Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") - } - } - .contextMenu{ - - if node != nil { - - Text("Num: \(String(node!.num))") - Text("Short Name: \(node?.user?.shortName ?? "????")") - Text("Long Name: \(node?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") - Text("Max Channels: \(String(node?.myInfo?.maxChannels ?? 0))") - Text("Bitrate: \(String(format: "%.2f", node?.myInfo?.bitrate ?? 0.00))") - Text("BLE RSSI: \(bleManager.connectedPeripheral.rssi)") - - } - } - if isUnsetRegion { - HStack { - NavigationLink { - LoRaConfig(node: node) - } label: { - Label("set.region", systemImage: "globe.americas.fill") - .foregroundColor(.red) - .font(.title) - } - } - } - } else { - - if bleManager.isConnecting { + + Section(header: Text("connected.radio").font(.title)) { + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected { HStack { Image(systemName: "antenna.radiowaves.left.and.right") .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.orange) + .imageScale(.large).foregroundColor(.green) .padding(.trailing) - if bleManager.timeoutTimerCount == 0 { - Text("connecting") - .font(.title3) - .foregroundColor(.orange) - } else { - VStack { - - Text("Connection Attempt \(bleManager.timeoutTimerCount) of 10") - .font(.callout) - .foregroundColor(.orange) + VStack(alignment: .leading) { + if node != nil { + Text(bleManager.connectedPeripheral.longName).font(.title2) } - } - } - .padding() - - } else { - - if bleManager.lastConnectionError.count > 0 { - Text(bleManager.lastConnectionError).font(.callout).foregroundColor(.red) - } - HStack { - Image(systemName: "antenna.radiowaves.left.and.right.slash") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.red) - .padding(.trailing) - Text("not.connected").font(.title3) - } - .padding() - } - } - } - .textCase(nil) - - if !self.bleManager.isConnected { - Section(header: Text("available.radios").font(.title)) { - ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name > $1.name })) { peripheral in - HStack { - Image(systemName: "circle.fill") - .imageScale(.large).foregroundColor(.gray) - .padding(.trailing) - Button(action: { - self.bleManager.stopScanning() - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { - - self.bleManager.disconnectPeripheral() + Text("ble.name").font(.caption)+Text(": \(bleManager.connectedPeripheral.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") + .font(.caption).foregroundColor(Color.gray) + if node != nil { + Text("firmware.version").font(.caption)+Text(": \(node?.myInfo?.firmwareVersion ?? NSLocalizedString("unknown", comment: "Unknown"))") + .font(.caption).foregroundColor(Color.gray) } - self.bleManager.connectTo(peripheral: peripheral.peripheral) - if userSettings.preferredPeripheralId == peripheral.peripheral.identifier.uuidString { - - isPreferredRadio = true + if bleManager.isSubscribed { + Text("subscribed").font(.caption) + .foregroundColor(.green) } else { - - isPreferredRadio = false + Text("communicating").font(.caption) + .foregroundColor(.orange) } - }) { - Text(peripheral.name).font(.title3) } Spacer() - VStack { - SignalStrengthIndicator(signalStrength: peripheral.getSignalStrength()) + VStack(alignment: .center) { + Text("preferred.radio").font(.caption2) + .multilineTextAlignment(.center) + .frame(width: 75) + Toggle("preferred.radio", isOn: $bleManager.preferredPeripheral) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .labelsHidden() + .onChange(of: bleManager.preferredPeripheral) { value in + if value { + if bleManager.connectedPeripheral != nil { + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString + userSettings.preferredNodeNum = bleManager.connectedPeripheral!.num + bleManager.preferredPeripheral = true + isPreferredRadio = true + } + } else { + + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { + + userSettings.preferredPeripheralId = "" + userSettings.preferredNodeNum = 0 + bleManager.preferredPeripheral = false + isPreferredRadio = false + } + } + } } - }.padding([.bottom, .top]) + } + .font(.caption).foregroundColor(Color.gray) + .padding([.top, .bottom]) + .swipeActions { + + Button(role: .destructive) { + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { + bleManager.disconnectPeripheral(reconnect: false) + isPreferredRadio = false + } + } label: { + Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") + } + } + .contextMenu{ + + if node != nil { + + if #available(iOS 16.2, *) { + Button { + if !liveActivityStarted { + #if canImport(ActivityKit) + print("Start live activity.") + startNodeActivity() + #endif + } else { + #if canImport(ActivityKit) + print("Stop live activity.") + endActivity() + #endif + } + } label: { + Label("Mesh Live Activity", systemImage: liveActivityStarted ? "stop" : "play") + } + } + Text("Num: \(String(node!.num))") + Text("Short Name: \(node?.user?.shortName ?? "????")") + Text("Long Name: \(node?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") + Text("Max Channels: \(String(node?.myInfo?.maxChannels ?? 0))") + Text("Bitrate: \(String(format: "%.2f", node?.myInfo?.bitrate ?? 0.00))") + Text("BLE RSSI: \(bleManager.connectedPeripheral.rssi)") + + } + } + if isUnsetRegion { + HStack { + NavigationLink { + LoRaConfig(node: node) + } label: { + Label("set.region", systemImage: "globe.americas.fill") + .foregroundColor(.red) + .font(.title) + } + } + } + } else { + + if bleManager.isConnecting { + HStack { + Image(systemName: "antenna.radiowaves.left.and.right") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.orange) + .padding(.trailing) + if bleManager.timeoutTimerCount == 0 { + Text("connecting") + .font(.title3) + .foregroundColor(.orange) + } else { + VStack { + + Text("Connection Attempt \(bleManager.timeoutTimerCount) of 10") + .font(.callout) + .foregroundColor(.orange) + } + } + } + .padding() + + } else { + + if bleManager.lastConnectionError.count > 0 { + Text(bleManager.lastConnectionError).font(.callout).foregroundColor(.red) + } + HStack { + Image(systemName: "antenna.radiowaves.left.and.right.slash") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.red) + .padding(.trailing) + Text("not.connected").font(.title3) + } + .padding() + } } - }.textCase(nil) - } + } + .textCase(nil) + + if !self.bleManager.isConnected { + Section(header: Text("available.radios").font(.title)) { + ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name > $1.name })) { peripheral in + HStack { + Image(systemName: "circle.fill") + .imageScale(.large).foregroundColor(.gray) + .padding(.trailing) + Button(action: { + self.bleManager.stopScanning() + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { + + self.bleManager.disconnectPeripheral() + } + self.bleManager.connectTo(peripheral: peripheral.peripheral) + if userSettings.preferredPeripheralId == peripheral.peripheral.identifier.uuidString { + + isPreferredRadio = true + } else { + + isPreferredRadio = false + } + }) { + Text(peripheral.name).font(.title3) + } + Spacer() + VStack { + SignalStrengthIndicator(signalStrength: peripheral.getSignalStrength()) + } + }.padding([.bottom, .top]) + } + }.textCase(nil) + } } else { Text("bluetooth.off") @@ -199,12 +221,12 @@ struct Connect: View { .font(.title) } } - + HStack(alignment: .center) { Spacer() - - #if targetEnvironment(macCatalyst) - + +#if targetEnvironment(macCatalyst) + if bleManager.connectedPeripheral != nil { Button(role: .destructive, action: { @@ -217,23 +239,23 @@ struct Connect: View { }) { Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") - + } .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) .padding() } - #endif - Spacer() - } - .padding(.bottom, 10) +#endif + Spacer() + } + .padding(.bottom, 10) } .navigationTitle("bluetooth") .navigationBarItems(leading: MeshtasticLogo(), trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) } .sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) { InvalidVersion(minimumVersion: self.bleManager.minimumVersion, version: self.bleManager.connectedVersion) @@ -270,7 +292,7 @@ struct Connect: View { .onAppear(perform: { self.bleManager.context = context self.bleManager.userSettings = userSettings - + // Ask for notification permission UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { @@ -286,6 +308,69 @@ struct Connect: View { } }) } +#if canImport(ActivityKit) +func startNodeActivity() { + if #available(iOS 16.2, *) { + liveActivityStarted = true + let timerSeconds = 300 + + let mostRecent = node?.telemetries?.lastObject as! TelemetryEntity + + let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown", connected: true, channelUtilization: mostRecent.channelUtilization, airtime: mostRecent.airUtilTx, batteryLevel: UInt32(mostRecent.batteryLevel)) + + let future = Date(timeIntervalSinceNow: Double(timerSeconds)) + + let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future) + + let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!) + + do { + let myActivity = try Activity.request(attributes: activityAttributes, content: activityContent, + pushType: nil) + print(" Requested MyActivity live activity. ID: \(myActivity.id)") + postNotification() + } catch let error { + print("Error requesting live activity: \(error.localizedDescription)") + } + } +} + +func endActivity() { + Task { + if #available(iOS 16.2, *) { + for activity in Activity.activities { + // Check if this is the activity associated with this order. + if activity.attributes.nodeNum == node?.num ?? 0 { + await activity.end(nil, dismissalPolicy: .immediate) + } + } + } + } +} +#endif + +#if os(iOS) +func postNotification() { + let timerSeconds = 300 + let content = UNMutableNotificationContent() + content.title = "Mesh Live Activity Over" + content.body = "Your timed mesh live activity is over." + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(timerSeconds), repeats: false) + let uuidString = UUID().uuidString + let request = UNNotificationRequest(identifier: uuidString, + content: content, trigger: trigger) + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.add(request) { (error) in + if error != nil { + // Handle any errors. + print("Error posting local notification: \(error?.localizedDescription ?? "no description")") + } else { + print("Posted local notification.") + } + } +} +#endif + func didDismissSheet() { bleManager.disconnectPeripheral(reconnect: false) } diff --git a/Widgets/Assets.xcassets/AccentColorDimmed.colorset/Contents.json b/Widgets/Assets.xcassets/AccentColorDimmed.colorset/Contents.json new file mode 100644 index 00000000..8892d1c1 --- /dev/null +++ b/Widgets/Assets.xcassets/AccentColorDimmed.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.420", + "green" : "0.470", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/LightIndigo.colorset/Contents.json b/Widgets/Assets.xcassets/LightIndigo.colorset/Contents.json new file mode 100644 index 00000000..45922d07 --- /dev/null +++ b/Widgets/Assets.xcassets/LightIndigo.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.56", + "green" : "0.96", + "red" : "0.32" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/LiveActivityBackground.colorset/Contents.json b/Widgets/Assets.xcassets/LiveActivityBackground.colorset/Contents.json new file mode 100644 index 00000000..7759d9ae --- /dev/null +++ b/Widgets/Assets.xcassets/LiveActivityBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.56", + "green" : "0.96", + "red" : "0.32" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.420", + "green" : "0.470", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Contents.json b/Widgets/Assets.xcassets/logo-black.imageset/Contents.json new file mode 100644 index 00000000..5ef35f53 --- /dev/null +++ b/Widgets/Assets.xcassets/logo-black.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Mesh_Logo_Black_Small.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Mesh_Logo_Black.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Mesh_Logo_Black_Large.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg b/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg new file mode 100644 index 00000000..e0f9bb19 --- /dev/null +++ b/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg b/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg new file mode 100644 index 00000000..a3eec0a0 --- /dev/null +++ b/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg b/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg new file mode 100644 index 00000000..b13ff954 --- /dev/null +++ b/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Contents.json b/Widgets/Assets.xcassets/logo-white.imageset/Contents.json new file mode 100644 index 00000000..c2d1f573 --- /dev/null +++ b/Widgets/Assets.xcassets/logo-white.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Mesh_Logo_White_Small.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Mesh_Logo_White.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Mesh_Logo_White_Large.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg b/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg new file mode 100644 index 00000000..b1bcd575 --- /dev/null +++ b/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg b/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg new file mode 100644 index 00000000..517dd23d --- /dev/null +++ b/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg b/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg new file mode 100644 index 00000000..fab02c18 --- /dev/null +++ b/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Widgets/MeshActivityAttributes.swift b/Widgets/MeshActivityAttributes.swift new file mode 100644 index 00000000..4b09b52d --- /dev/null +++ b/Widgets/MeshActivityAttributes.swift @@ -0,0 +1,29 @@ +// +// MeshActivityAttributes.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 3/1/23. +// + +#if canImport(ActivityKit) + +import ActivityKit +import WidgetKit +import SwiftUI + +struct MeshActivityAttributes: ActivityAttributes { + public typealias MyActivityStatus = ContentState + public struct ContentState: Codable, Hashable { + // Dynamic stateful properties about your activity go here! + var timerRange: ClosedRange + } + + // Fixed non-changing properties about your activity go here! + var nodeNum: Int + var name: String + var connected: Bool + var channelUtilization: Float + var airtime: Float + var batteryLevel: UInt32 +} +#endif diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index e53a057d..d2bec44b 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -4,74 +4,198 @@ // // Created by Garth Vander Houwen on 2/28/23. // - +#if canImport(ActivityKit) import ActivityKit import WidgetKit import SwiftUI -struct WidgetsAttributes: ActivityAttributes { - public struct ContentState: Codable, Hashable { - // Dynamic stateful properties about your activity go here! - var value: Int - } - - // Fixed non-changing properties about your activity go here! - var name: String -} - +@available(iOS 16.2, *) struct WidgetsLiveActivity: Widget { var body: some WidgetConfiguration { - ActivityConfiguration(for: WidgetsAttributes.self) { context in - // Lock screen/banner UI goes here - VStack { - Text("Hello") - } - .activityBackgroundTint(Color.cyan) - .activitySystemActionForegroundColor(Color.black) + + ActivityConfiguration(for: MeshActivityAttributes.self) { context in + LiveActivityView(nodeName: context.attributes.name, connected: context.attributes.connected, channelUtilization: context.attributes.channelUtilization, airtime: context.attributes.airtime, batteryLevel: context.attributes.batteryLevel, timerRange: context.state.timerRange) + .widgetURL(URL(string: "meshtastic://node/\(context.attributes.name)")) } dynamicIsland: { context in DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + NodeInfoView(nodeName: context.attributes.name, connected: context.attributes.connected, channelUtilization: context.attributes.channelUtilization, airtime: context.attributes.airtime, batteryLevel: context.attributes.batteryLevel) + .tint(Color("LightIndigo")) + .padding(.top) + } // Expanded UI goes here. Compose the expanded UI through // various regions, like leading/trailing/center/bottom - DynamicIslandExpandedRegion(.leading) { - Text("Leading") - } - DynamicIslandExpandedRegion(.trailing) { - Text("Trailing") - } - DynamicIslandExpandedRegion(.bottom) { - Text("Bottom") - // more content - } + DynamicIslandExpandedRegion(.trailing, priority: 1) { + HStack(alignment: .lastTextBaseline) { + + Spacer() + TimerView(timerRange: context.state.timerRange) + .tint(Color("LightIndigo")) + } + .padding(.top) + + } + } compactLeading: { - Text("L") + Image("logo-black") + .resizable() + .frame(width: 30.0) + .padding(4) + .background(.green.gradient, in: ContainerRelativeShape()) } compactTrailing: { - Text("T") + Text(timerInterval: context.state.timerRange, countsDown: true) + .monospacedDigit() + .foregroundColor(Color("LightIndigo")) + .frame(width: 40) } minimal: { - Text("Min") + Image("logo-black") + .resizable() + .frame(width: 24.0) + .padding(4) + .background(.green.gradient, in: ContainerRelativeShape()) } - .widgetURL(URL(string: "http://www.apple.com")) - .keylineTint(Color.red) + .contentMargins(.trailing, 32, for: .expanded) + .contentMargins([.leading, .top, .bottom], 6, for: .compactLeading) + .contentMargins(.all, 6, for: .minimal) + .widgetURL(URL(string: "meshtastic://node/\(context.attributes.name)")) } } } +@available(iOS 16.2, *) struct WidgetsLiveActivity_Previews: PreviewProvider { - static let attributes = WidgetsAttributes(name: "Me") - static let contentState = WidgetsAttributes.ContentState(value: 3) + static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "Meshtastic 8E6G", connected: true, channelUtilization: 0.24, airtime: 0.87, batteryLevel: 39) + static let state = MeshActivityAttributes.ContentState( + timerRange: Date.now...Date(timeIntervalSinceNow: 3600)) static var previews: some View { attributes - .previewContext(contentState, viewKind: .dynamicIsland(.compact)) - .previewDisplayName("Island Compact") + .previewContext(state, viewKind: .dynamicIsland(.compact)) + .previewDisplayName("Compact") + attributes + .previewContext(state, viewKind: .dynamicIsland(.minimal)) + .previewDisplayName("Minimal") attributes - .previewContext(contentState, viewKind: .dynamicIsland(.expanded)) - .previewDisplayName("Island Expanded") - attributes - .previewContext(contentState, viewKind: .dynamicIsland(.minimal)) - .previewDisplayName("Minimal") - attributes - .previewContext(contentState, viewKind: .content) - .previewDisplayName("Notification") + .previewContext(state, viewKind: .dynamicIsland(.expanded)) + .previewDisplayName("Expanded") + attributes + .previewContext(state, viewKind: .content) + .previewDisplayName("Notification") } } + +@available(iOS 16.2, *) +struct LiveActivityView: View { + @Environment(\.colorScheme) private var colorScheme + @Environment(\.isLuminanceReduced) var isLuminanceReduced + + var nodeName: String + var connected: Bool + var channelUtilization: Float + var airtime: Float + var batteryLevel: UInt32 + var timerRange: ClosedRange + + var body: some View { + HStack { + Image(colorScheme == .light ? "logo-black" : "logo-white") + .clipShape(ContainerRelativeShape()) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + NodeInfoView(nodeName: nodeName, connected: connected, channelUtilization: channelUtilization, airtime: airtime, batteryLevel: batteryLevel) + Spacer() + TimerView(timerRange: timerRange) + } + .tint(.primary) + .padding([.leading, .top, .bottom]) + .padding(.trailing, 32) + .activityBackgroundTint(colorScheme == .light ? Color("LiveActivityBackground") : Color("AccentColorDimmed")) + .activitySystemActionForegroundColor(.primary) + } +} + +struct NodeInfoView: View { + @Environment(\.isLuminanceReduced) var isLuminanceReduced + + var nodeName: String + var connected: Bool + var channelUtilization: Float + var airtime: Float + var batteryLevel: UInt32 + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Text(nodeName) + .font(.title3) + .fontWeight(.semibold) + .foregroundStyle(.tint) + .fixedSize() + Text(connected ? "Connected" : "Not Connected") + .font(.callout) + .fontWeight(.semibold) + .foregroundStyle(.tint) + .fixedSize() + Text("\(String(format: "Ch. Util: %.2f", channelUtilization))%") + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + .fixedSize() + Text("\(String(format: "Airtime: %.2f", airtime))%") + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + .fixedSize() + Text("Battery Level: \(batteryLevel)%") + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + .fixedSize() + } + } +} + +struct TimerView: View { + @Environment(\.isLuminanceReduced) var isLuminanceReduced + + var timerRange: ClosedRange + + var body: some View { + VStack(alignment: .trailing) { + Text(timerInterval: timerRange, countsDown: true) + .monospacedDigit() + .multilineTextAlignment(.trailing) + .frame(width: 80) + .font(.title3) + .fontWeight(.semibold) + .foregroundStyle(.tint) + Image(systemName: "timer") + .resizable() + .foregroundStyle(.secondary) + .frame(width: 30, height: 30) + .padding(.trailing) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + } + } +} + +struct ExpandedTrailingView: View { + var nodeName: String + var connected: Bool + var channelUtilization: Float + var airtime: Float + var batteryLevel: UInt32 + var timerInterval: ClosedRange + + var body: some View { + HStack(alignment: .lastTextBaseline) { + + Spacer() + TimerView(timerRange: timerInterval) + } + .tint(Color("LightIndigo")) + } +} +#endif diff --git a/WidgetsExtension.entitlements b/WidgetsExtension.entitlements new file mode 100644 index 00000000..ee95ab7e --- /dev/null +++ b/WidgetsExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + +