Mesh Activity

This commit is contained in:
Garth Vander Houwen 2023-03-01 23:41:10 -08:00
parent 9a4de51281
commit 4ca3cf89ab
16 changed files with 845 additions and 330 deletions

View file

@ -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 = "<group>"; };
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = "<group>"; };
@ -164,7 +193,6 @@
DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = "<group>"; };
DD41582928585C32009B0E59 /* RangeTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeTestConfig.swift; sourceTree = "<group>"; };
DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWeatherForecast.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -262,6 +290,14 @@
DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = "<group>"; };
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = "<group>"; };
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = "<group>"; };
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 = "<group>"; };
DDDE59FA29AF163D00490C6C /* WidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsLiveActivity.swift; sourceTree = "<group>"; };
DDDE59FC29AF163D00490C6C /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = "<group>"; };
DDDE5A0029AF163E00490C6C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DDDE5A0429AF163E00490C6C /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = "<group>"; };
DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = "<group>"; };
DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = "<group>"; };
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = "<group>"; };
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -629,26 +667,22 @@
path = Persistence;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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 */

View file

@ -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<MeshActivityAttributes>.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<MeshActivityAttributes>.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)
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 55" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.802386,0,0,0.460028,-421.748,-122.127)">
<g transform="matrix(0.579082,0,0,1.01004,460.975,-39.6867)">
<path d="M250.908,330.267L193.126,415.005L180.938,406.694L244.802,313.037C246.174,311.024 248.453,309.819 250.889,309.816C253.326,309.814 255.606,311.015 256.982,313.026L320.994,406.536L308.821,414.869L250.908,330.267Z"/>
</g>
<g transform="matrix(0.582378,0,0,1.01579,485.019,-211.182)">
<path d="M87.642,581.398L154.757,482.977L142.638,474.713L75.523,573.134L87.642,581.398Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 55" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.353462,0,0,0.202442,-296.058,-23.2047)">
<g transform="matrix(1.40256,0,0,2.44636,669.626,-634.831)">
<path d="M250.905,326.97L191.589,413.957L182.475,407.742L246.339,314.085C247.365,312.58 249.069,311.678 250.891,311.677C252.713,311.675 254.418,312.573 255.447,314.077L319.459,407.587L310.356,413.818L250.905,326.97Z"/>
</g>
<g transform="matrix(1.39424,0,0,2.43184,733.087,-1033.78)">
<path d="M86.181,580.402L153.296,481.981L144.099,475.709L76.984,574.13L86.181,580.402Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 50 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.10116,0,0,0.631321,-207.228,-202.833)">
<g transform="matrix(0.201804,0,0,0.351989,167.289,216.061)">
<path d="M250.912,334.664L195.175,416.403L178.889,405.297L242.753,311.64C244.587,308.95 247.631,307.339 250.887,307.336C254.143,307.333 257.19,308.938 259.029,311.625L323.04,405.135L306.774,416.27L250.912,334.664Z"/>
</g>
<g transform="matrix(0.205009,0,0,0.357578,174.224,154.227)">
<path d="M89.878,582.923L156.993,484.502L140.402,473.188L73.287,571.609L89.878,582.923Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -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
}
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 55" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.802386,0,0,0.460028,-421.748,-122.127)">
<g transform="matrix(0.579082,0,0,1.01004,460.975,-39.6867)">
<path d="M250.908,330.267L193.126,415.005L180.938,406.694L244.802,313.037C246.174,311.024 248.453,309.819 250.889,309.816C253.326,309.814 255.606,311.015 256.982,313.026L320.994,406.536L308.821,414.869L250.908,330.267Z" style="fill:white;"/>
</g>
<g transform="matrix(0.582378,0,0,1.01579,485.019,-211.182)">
<path d="M87.642,581.398L154.757,482.977L142.638,474.713L75.523,573.134L87.642,581.398Z" style="fill:white;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 55" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.353462,0,0,0.202442,-296.058,-23.2047)">
<g transform="matrix(1.40256,0,0,2.44636,669.626,-634.831)">
<path d="M250.905,326.97L191.589,413.957L182.475,407.742L246.339,314.085C247.365,312.58 249.069,311.678 250.891,311.677C252.713,311.675 254.418,312.573 255.447,314.077L319.459,407.587L310.356,413.818L250.905,326.97Z" style="fill:white;"/>
</g>
<g transform="matrix(1.39424,0,0,2.43184,733.087,-1033.78)">
<path d="M86.181,580.402L153.296,481.981L144.099,475.709L76.984,574.13L86.181,580.402Z" style="fill:white;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 50 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.10116,0,0,0.631321,-207.228,-202.833)">
<g transform="matrix(0.201804,0,0,0.351989,167.289,216.061)">
<path d="M250.912,334.664L195.175,416.403L178.889,405.297L242.753,311.64C244.587,308.95 247.631,307.339 250.887,307.336C254.143,307.333 257.19,308.938 259.029,311.625L323.04,405.135L306.774,416.27L250.912,334.664Z" style="fill:white;"/>
</g>
<g transform="matrix(0.205009,0,0,0.357578,174.224,154.227)">
<path d="M89.878,582.923L156.993,484.502L140.402,473.188L73.287,571.609L89.878,582.923Z" style="fill:white;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -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<Date>
}
// 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

View file

@ -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<Date>
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<Date>
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<Date>
var body: some View {
HStack(alignment: .lastTextBaseline) {
Spacer()
TimerView(timerRange: timerInterval)
}
.tint(Color("LightIndigo"))
}
}
#endif

View file

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