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/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index ecafa677..75b3fb2e 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -8,6 +8,9 @@ import Foundation import CoreData import SwiftUI +#if canImport(ActivityKit) +import ActivityKit +#endif func generateMessageMarkdown (message: String) -> String { @@ -635,6 +638,9 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage if connectedNode != Int64(packet.from) { let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.telemetry.received %@", comment: "Telemetry received for: %@"), String(packet.from)) MeshLogger.log("📈 \(logString)") + } else { + // If it is the connected node + } let telemetry = TelemetryEntity(context: context) @@ -674,6 +680,28 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage // Only log telemetry from the mesh not the connected device if connectedNode != Int64(packet.from) { print("💾 Telemetry Saved for Node: \(packet.from)") + } else if telemetry.metricsType == 0 { + // Update our live activity if there is one running + #if !targetEnvironment(macCatalyst) + if #available(iOS 16.2, *) { + + let oneMinuteLater = Calendar.current.date(byAdding: .minute, value: (Int(1) ), to: Date())! + let date = Date.now...oneMinuteLater + let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(timerRange: date, connected: true, channelUtilization: telemetry.channelUtilization, airtime: telemetry.airUtilTx, batteryLevel: UInt32(telemetry.batteryLevel)) + + let alertConfiguration = AlertConfiguration(title: "Mesh activity update", body: "Updated Device Metrics Data.", sound: .default) + let updatedContent = ActivityContent(state: updatedMeshStatus, staleDate: nil) + print("Update live activity.") + + let meshActivity = Activity.activities.first(where: { $0.attributes.nodeNum == connectedNode }) + if meshActivity != nil { + Task { + await meshActivity?.update(updatedContent, alertConfiguration: alertConfiguration) + //await meshActivity?.update(updatedContent) + } + } + } + #endif } } catch { context.rollback() diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 43a0ebf5..799c20e0 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -10,6 +10,9 @@ import MapKit import CoreData import CoreLocation import CoreBluetooth +#if canImport(ActivityKit) +import ActivityKit +#endif struct Connect: View { @@ -19,6 +22,7 @@ struct Connect: View { @State var node: NodeInfoEntity? = nil @State var isUnsetRegion = false @State var invalidFirmwareVersion = false + @State var liveActivityStarted = false @State var presentingSwitchPreferredPeripheral = false @State var selectedPeripherialId = "" @@ -32,6 +36,54 @@ struct Connect: View { 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 + } + } + } .resizable() .symbolRenderingMode(.hierarchical) .foregroundColor(.green) @@ -72,13 +124,29 @@ struct Connect: View { 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 { @@ -187,6 +255,7 @@ struct Connect: View { Spacer() #if targetEnvironment(macCatalyst) + if bleManager.connectedPeripheral != nil { Button(role: .destructive, action: { @@ -263,6 +332,69 @@ struct Connect: View { } }) } +#if canImport(ActivityKit) +func startNodeActivity() { + if #available(iOS 16.2, *) { + liveActivityStarted = true + let timerSeconds = 60 + + let mostRecent = node?.telemetries?.lastObject as! TelemetryEntity + + let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown") + + let future = Date(timeIntervalSinceNow: Double(timerSeconds)) + + let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent.channelUtilization, airtime: mostRecent.airUtilTx, batteryLevel: UInt32(mostRecent.batteryLevel)) + + 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)") + } catch let error { + print("Error requesting live activity: \(error.localizedDescription)") + } + } +} + +func endActivity() { + liveActivityStarted = false + 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 = 60 + 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..5159898f --- /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 MeshActivityStatus = ContentState + public struct ContentState: Codable, Hashable { + // Dynamic stateful properties about your activity go here! + var timerRange: ClosedRange + var connected: Bool + var channelUtilization: Float + var airtime: Float + var batteryLevel: UInt32 + } + + // Fixed non-changing properties about your activity go here! + var nodeNum: Int + var name: String +} +#endif diff --git a/Widgets/Widgets.swift b/Widgets/Widgets.swift index a060da8b..2dccff37 100644 --- a/Widgets/Widgets.swift +++ b/Widgets/Widgets.swift @@ -1,20 +1,66 @@ +//// +//// Widgets.swift +//// Widgets +//// +//// Created by Garth Vander Houwen on 2/28/23. +//// // -// WidgetsBundle.swift -// Widgets +//import WidgetKit +//import SwiftUI // -// Created by Garth Vander Houwen on 2/28/23. +//struct Provider: TimelineProvider { +// func placeholder(in context: Context) -> SimpleEntry { +// SimpleEntry(date: Date()) +// } // - -import WidgetKit -import SwiftUI - -@main -struct Widgets: WidgetBundle { - var body: some Widget { - - // MARK: - Live Activity Widgets - #if canImport(ActivityKit) - MeshActivityWidget() - #endif - } -} +// func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { +// let entry = SimpleEntry(date: Date()) +// completion(entry) +// } +// +// func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { +// var entries: [SimpleEntry] = [] +// +// // Generate a timeline consisting of five entries an hour apart, starting from the current date. +// let currentDate = Date() +// for hourOffset in 0 ..< 5 { +// let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! +// let entry = SimpleEntry(date: entryDate) +// entries.append(entry) +// } +// +// let timeline = Timeline(entries: entries, policy: .atEnd) +// completion(timeline) +// } +//} +// +//struct SimpleEntry: TimelineEntry { +// let date: Date +//} +// +//struct WidgetsEntryView : View { +// var entry: Provider.Entry +// +// var body: some View { +// Text(entry.date, style: .time) +// } +//} +// +//struct Widgets: Widget { +// let kind: String = "Widgets" +// +// var body: some WidgetConfiguration { +// StaticConfiguration(kind: kind, provider: Provider()) { entry in +// WidgetsEntryView(entry: entry) +// } +// .configurationDisplayName("My Widget") +// .description("This is an example widget.") +// } +//} +// +//struct Widgets_Previews: PreviewProvider { +// static var previews: some View { +// WidgetsEntryView(entry: SimpleEntry(date: Date())) +// .previewContext(WidgetPreviewContext(family: .systemSmall)) +// } +//} diff --git a/Widgets/WidgetsBundle.swift b/Widgets/WidgetsBundle.swift new file mode 100644 index 00000000..950954a7 --- /dev/null +++ b/Widgets/WidgetsBundle.swift @@ -0,0 +1,21 @@ +// +// WidgetsBundle.swift +// Widgets +// +// Created by Garth Vander Houwen on 2/28/23. +// + +import WidgetKit +import SwiftUI + +@available(iOSApplicationExtension 16.2, *) +@main +struct WidgetsBundle: WidgetBundle { + var body: some Widget { + //Widgets() + #if canImport(ActivityKit) + WidgetsLiveActivity() + #endif + + } +} diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index e53a057d..744539f7 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -4,74 +4,210 @@ // // 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, channelUtilization: context.state.channelUtilization, airtime: context.state.airtime, batteryLevel: context.state.batteryLevel, timerRange: context.state.timerRange) + .widgetURL(URL(string: "meshtastic://node/\(context.attributes.name)")) } dynamicIsland: { context in DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + NodeInfoView(nodeName: context.attributes.name, timerRange: context.state.timerRange, channelUtilization: context.state.channelUtilization, airtime: context.state.airtime, batteryLevel: context.state.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") + static let state = MeshActivityAttributes.ContentState( + timerRange: Date.now...Date(timeIntervalSinceNow: 3600), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39) 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, timerRange: timerRange, 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 timerRange: ClosedRange + 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("\(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() + Text(Date().formatted()) + .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: .center) { + Text("NEXT") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + .fixedSize() + Text("UPDATE") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + .fixedSize() + Text(timerInterval: timerRange, countsDown: true) + .monospacedDigit() + .multilineTextAlignment(.center) + .frame(width: 80) + .font(.callout) + .fontWeight(.semibold) + .foregroundStyle(.tint) + Image(systemName: "timer") + .resizable() + .foregroundStyle(.secondary) + .frame(width: 30, height: 30) + .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 + + +