Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions appleApp/FTW.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@
<array>
<string>$(FTW_APP_GROUP)</string>
</array>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.usernotifications.time-sensitive</key>
<true/>
</dict>
</plist>
137 changes: 137 additions & 0 deletions appleApp/FTW.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
F7A000000000000000000065 /* NOTICE in Copy the license texts */ = {isa = PBXBuildFile; fileRef = F7A000000000000000000061 /* NOTICE */; };
F7A000000000000000000066 /* LICENSING.md in Copy the license texts */ = {isa = PBXBuildFile; fileRef = F7A000000000000000000062 /* LICENSING.md */; };
F7A000000000000000000034 /* FTWWidgets.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F7A000000000000000000040 /* FTWWidgets.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
F7A000000000000000000070 /* FTWShared in Frameworks */ = {isa = PBXBuildFile; productRef = F7A000000000000000000071 /* FTWShared */; };
F7A000000000000000000072 /* FTWNotify.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F7A000000000000000000074 /* FTWNotify.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -31,6 +33,13 @@
remoteGlobalIDString = F7A000000000000000000010;
remoteInfo = FTW;
};
F7A000000000000000000073 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F7A000000000000000000001 /* Project object */;
proxyType = 1;
remoteGlobalIDString = F7A000000000000000000078;
remoteInfo = FTWNotify;
};
/* End PBXContainerItemProxy section */

/* Begin PBXCopyFilesBuildPhase section */
Expand All @@ -41,6 +50,7 @@
dstSubfolderSpec = 13;
files = (
F7A000000000000000000034 /* FTWWidgets.appex in Embed Foundation Extensions */,
F7A000000000000000000072 /* FTWNotify.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -72,6 +82,9 @@
F7A000000000000000000062 /* LICENSING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = LICENSING.md; path = ../LICENSING.md; sourceTree = SOURCE_ROOT; };
F7A000000000000000000040 /* FTWWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FTWWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
F7A000000000000000000100 /* FTWUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FTWUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F7A000000000000000000074 /* FTWNotify.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FTWNotify.appex; sourceTree = BUILT_PRODUCTS_DIR; };
F7A000000000000000000075 /* FTWNotify.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FTWNotify.entitlements; sourceTree = "<group>"; };
F7A000000000000000000076 /* FTWNotify-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FTWNotify-Info.plist"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand All @@ -95,6 +108,11 @@
path = FTWCharts;
sourceTree = "<group>";
};
F7A000000000000000000077 /* FTWNotify */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = FTWNotify;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -122,6 +140,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F7A00000000000000000007A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F7A000000000000000000070 /* FTWShared in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
Expand All @@ -132,11 +158,14 @@
F7A000000000000000000041 /* FTWWidgets */,
F7A000000000000000000101 /* FTWUITests */,
F7A000000000000000000110 /* FTWCharts */,
F7A000000000000000000077 /* FTWNotify */,
F7A000000000000000000008 /* FTW.xcconfig */,
F7A000000000000000000006 /* FTW.entitlements */,
F7A000000000000000000009 /* FTW-Info.plist */,
F7A000000000000000000007 /* FTWWidgets.entitlements */,
F7A00000000000000000000A /* FTWWidgets-Info.plist */,
F7A000000000000000000075 /* FTWNotify.entitlements */,
F7A000000000000000000076 /* FTWNotify-Info.plist */,
F7A000000000000000000063 /* License texts */,
F7A000000000000000000004 /* Products */,
);
Expand All @@ -158,6 +187,7 @@
F7A000000000000000000005 /* FTW.app */,
F7A000000000000000000040 /* FTWWidgets.appex */,
F7A000000000000000000100 /* FTWUITests.xctest */,
F7A000000000000000000074 /* FTWNotify.appex */,
);
name = Products;
sourceTree = "<group>";
Expand All @@ -180,6 +210,7 @@
);
dependencies = (
F7A000000000000000000037 /* PBXTargetDependency */,
F7A00000000000000000007C /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
F7A000000000000000000003 /* FTW */,
Expand Down Expand Up @@ -239,6 +270,29 @@
productReference = F7A000000000000000000100 /* FTWUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
F7A000000000000000000078 /* FTWNotify */ = {
isa = PBXNativeTarget;
buildConfigurationList = F7A00000000000000000007F /* Build configuration list for PBXNativeTarget "FTWNotify" */;
buildPhases = (
F7A000000000000000000079 /* Sources */,
F7A00000000000000000007A /* Frameworks */,
F7A00000000000000000007B /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
F7A000000000000000000077 /* FTWNotify */,
);
name = FTWNotify;
packageProductDependencies = (
F7A000000000000000000071 /* FTWShared */,
);
productName = FTWNotify;
productReference = F7A000000000000000000074 /* FTWNotify.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
Expand All @@ -259,6 +313,9 @@
CreatedOnToolsVersion = 27.0;
TestTargetID = F7A000000000000000000010;
};
F7A000000000000000000078 = {
CreatedOnToolsVersion = 27.0;
};
};
};
buildConfigurationList = F7A000000000000000000020 /* Build configuration list for PBXProject "FTW" */;
Expand All @@ -282,6 +339,7 @@
F7A000000000000000000010 /* FTW */,
F7A000000000000000000050 /* FTWWidgets */,
F7A000000000000000000102 /* FTWUITests */,
F7A000000000000000000078 /* FTWNotify */,
);
};
/* End PBXProject section */
Expand All @@ -308,6 +366,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F7A00000000000000000007B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
Expand Down Expand Up @@ -356,6 +421,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F7A000000000000000000079 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
Expand All @@ -369,6 +441,11 @@
target = F7A000000000000000000010 /* FTW */;
targetProxy = F7A000000000000000000106 /* PBXContainerItemProxy */;
};
F7A00000000000000000007C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F7A000000000000000000078 /* FTWNotify */;
targetProxy = F7A000000000000000000073 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
Expand Down Expand Up @@ -567,6 +644,52 @@
};
name = Release;
};
F7A00000000000000000007D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = FTWNotify.entitlements;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "FTWNotify-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = FTW;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = energy.ftw.app.notify;
PRODUCT_NAME = FTWNotify;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
F7A00000000000000000007E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = FTWNotify.entitlements;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "FTWNotify-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = FTW;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = energy.ftw.app.notify;
PRODUCT_NAME = FTWNotify;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
Expand Down Expand Up @@ -606,6 +729,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F7A00000000000000000007F /* Build configuration list for PBXNativeTarget "FTWNotify" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F7A00000000000000000007D /* Debug */,
F7A00000000000000000007E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
Expand All @@ -631,6 +763,11 @@
package = F7A000000000000000000015 /* XCLocalSwiftPackageReference "FTWKit" */;
productName = FTWShared;
};
F7A000000000000000000071 /* FTWShared */ = {
isa = XCSwiftPackageProductDependency;
package = F7A000000000000000000015 /* XCLocalSwiftPackageReference "FTWKit" */;
productName = FTWShared;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = F7A000000000000000000001 /* Project object */;
Expand Down
6 changes: 6 additions & 0 deletions appleApp/FTW/FTWApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import WidgetKit
struct FTWApp: App {
@State private var app: AppModel
@Environment(\.scenePhase) private var scenePhase
/// Only for what SwiftUI has no door for: the push token, and buttons
/// on a notification. It starts after `init`, so the runner is there.
@UIApplicationDelegateAdaptor(AppDelegate.self) private var delegate
private let network: NetworkWatch

init() {
Expand All @@ -35,6 +38,9 @@ struct FTWApp: App {
#if DEBUG
// For screenshots: `-FTWDemo YES` opens the simulated home.
if UserDefaults.standard.bool(forKey: "FTWDemo") { model.startDemo() }
// For end-to-end tests: `-FTWPairLink <url>` offers a pairing link
// as a scanned QR would, where the simulator has no camera.
if let link = UserDefaults.standard.string(forKey: "FTWPairLink") { model.offer(link) }
#endif
_app = State(initialValue: model)
network = NetworkWatch { [weak model] in model?.networkOnline() }
Expand Down
24 changes: 22 additions & 2 deletions appleApp/FTW/Intents/IntentRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ final class IntentRunner: CommandRunner {
await perform(command)
}

private func perform(_ command: RemoteCommand) async -> RemoteOutcome {
/// A notification's button: the same path, on the charger its event
/// named.
func run(_ command: RemoteCommand, loadpointID: String) async -> RemoteOutcome {
await perform(command, loadpointID: loadpointID)
}

private func perform(_ command: RemoteCommand, loadpointID: String? = nil) async -> RemoteOutcome {
guard let home = await begin() else {
// No home on this phone: a Live Activity left from one can only
// mislead, and its button has nothing to act on.
Expand All @@ -57,7 +63,7 @@ final class IntentRunner: CommandRunner {
}
defer { end() }
let started = ContinuousClock.now
let out = await home.loadpoints.perform(command)
let out = await home.loadpoints.perform(command, loadpointID: loadpointID)
// The system redraws the Control when this returns: the record it
// reads must already hold the box's answer, or the tile would flip
// back and then forward again.
Expand Down Expand Up @@ -99,6 +105,20 @@ final class IntentRunner: CommandRunner {
app.home?.loadpoints.answer(locked: DeviceLock.isLocked)
}

/// Whether `siteId` is the home this app opens. A command from a push
/// goes only there; the demo never takes one, except in a debug build
/// that seeded it as "demo" for a test.
func opens(siteId: String) -> Bool {
if app.isDemo {
#if DEBUG
return siteId == "demo"
#else
return false
#endif
}
return (app.site?.siteId ?? app.sites.current()?.siteId) == siteId
}

/// The home, with a session on its way. A launch for an intent has
/// already started connecting; an app whose session was closed in the
/// background opens a new one.
Expand Down
5 changes: 5 additions & 0 deletions appleApp/FTW/Intents/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* A push from FTW Cloud, as Apple shows it if the notification extension
does not run. The extension writes every other line itself. Elsewhere
the English is the key, so nothing else is listed here. */
"push.title" = "Something happened at home";
"push.generic" = "Open FTW to see what.";
5 changes: 5 additions & 0 deletions appleApp/FTW/Intents/sv.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@
"On" = "På";
"Open FTW" = "Öppna FTW";
"Reason" = "Orsak";

/* A push from FTW Cloud, as Apple shows it if the notification extension
does not run. The extension writes every other line itself. */
"push.title" = "Något hände hemma";
"push.generic" = "Öppna FTW för att se vad.";
Loading
Loading