receiving notifications now updates ui properly

This commit is contained in:
Tom Caputi 2023-10-28 03:07:56 -04:00
parent c2e043e450
commit 372e946419
13 changed files with 319 additions and 97 deletions

View file

@ -538,17 +538,19 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APP_BASE_URL = "http://192.168.1.7";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = ntfy/ntfy.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"ntfy/Assets/Preview Content\"";
DEVELOPMENT_TEAM = YXQ4AMS4B4;
DEVELOPMENT_TEAM = MZWHX5Z44T;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ntfy/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ntfy;
INFOPLIST_KEY_NSCameraUsageDescription = "We need access to the camera to scan QR codes.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
@ -560,7 +562,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = io.heckel.ntfy;
PRODUCT_BUNDLE_IDENTIFIER = com.tcaputi.ntfy;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
@ -572,17 +574,19 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APP_BASE_URL = "http://192.168.1.7";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = ntfy/ntfy.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"ntfy/Assets/Preview Content\"";
DEVELOPMENT_TEAM = YXQ4AMS4B4;
DEVELOPMENT_TEAM = MZWHX5Z44T;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ntfy/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ntfy;
INFOPLIST_KEY_NSCameraUsageDescription = "We need access to the camera to scan QR codes.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
@ -594,7 +598,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = io.heckel.ntfy;
PRODUCT_BUNDLE_IDENTIFIER = com.tcaputi.ntfy;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
@ -605,10 +609,11 @@
9474F1ED282F3FFD00CDE4DD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APP_BASE_URL = "http://192.168.1.7";
CODE_SIGN_ENTITLEMENTS = ntfyNSE/ntfyNSE.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = YXQ4AMS4B4;
DEVELOPMENT_TEAM = MZWHX5Z44T;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ntfyNSE/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ntfyNSE;
@ -620,7 +625,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = io.heckel.ntfy.ntfyNSE;
PRODUCT_BUNDLE_IDENTIFIER = com.tcaputi.ntfy.ntfyNSE;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@ -632,10 +637,11 @@
9474F1EE282F3FFD00CDE4DD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APP_BASE_URL = "http://192.168.1.7";
CODE_SIGN_ENTITLEMENTS = ntfyNSE/ntfyNSE.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = YXQ4AMS4B4;
DEVELOPMENT_TEAM = MZWHX5Z44T;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ntfyNSE/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ntfyNSE;
@ -647,7 +653,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = io.heckel.ntfy.ntfyNSE;
PRODUCT_BUNDLE_IDENTIFIER = com.tcaputi.ntfy.ntfyNSE;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;

View file

@ -23,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "cfa854c9c1073c4d1b83b20dfcb1ef7ceb85388b",
"version" : "9.0.0"
"revision" : "7e80c25b51c2ffa238879b07fbfc5baa54bb3050",
"version" : "9.6.0"
}
},
{
@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "6a3123fab90f3884167990bee9bb30097d99c98c",
"version" : "9.0.0"
"revision" : "c1cfde8067668027b23a42c29d11c246152fe046",
"version" : "9.6.0"
}
},
{
@ -86,8 +86,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "7ee9ef9f627d85cbe1b8c4f49a3ed26eed216c77",
"version" : "2.30908.0"
"revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692",
"version" : "2.30909.0"
}
},
{

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9474F1BC282F2AA700CDE4DD"
BuildableName = "ntfy.app"
BlueprintName = "ntfy"
ReferencedContainer = "container:ntfy.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9474F1BC282F2AA700CDE4DD"
BuildableName = "ntfy.app"
BlueprintName = "ntfy"
ReferencedContainer = "container:ntfy.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9474F1BC282F2AA700CDE4DD"
BuildableName = "ntfy.app"
BlueprintName = "ntfy"
ReferencedContainer = "container:ntfy.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9474F1E3282F3FFD00CDE4DD"
BuildableName = "ntfyNSE.appex"
BlueprintName = "ntfyNSE"
ReferencedContainer = "container:ntfy.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9474F1BC282F2AA700CDE4DD"
BuildableName = "ntfy.app"
BlueprintName = "ntfy"
ReferencedContainer = "container:ntfy.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<RemoteRunnable
runnableDebuggingMode = "0"
BundleIdentifier = "com.tcaputi.ntfy"
RemotePath = "/var/containers/Bundle/Application/508E12DE-12D7-4A45-8D44-B351AFC4BF23/ntfy.app">
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9474F1BC282F2AA700CDE4DD"
BuildableName = "ntfy.app"
BlueprintName = "ntfy"
ReferencedContainer = "container:ntfy.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9474F1BC282F2AA700CDE4DD"
BuildableName = "ntfy.app"
BlueprintName = "ntfy"
ReferencedContainer = "container:ntfy.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -13,11 +13,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, ObservableObject {
// Implements navigation from notifications, see https://stackoverflow.com/a/70731861/1440785
@Published var selectedBaseUrl: String? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
Log.d(tag, "Launching AppDelegate")
FirebaseApp.configure()
FirebaseConfiguration.shared.setLoggerLevel(.max)
// Register app permissions for push notifications
UNUserNotificationCenter.current().delegate = self
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
guard success else {
Log.e(self.tag, "Failed to register for local push notifications", error)
@ -29,12 +34,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, ObservableObject {
// Register too receive remote notifications
application.registerForRemoteNotifications()
// Set self as messaging delegate
Messaging.messaging().delegate = self
// Register to "~poll" topic
Messaging.messaging().subscribe(toTopic: pollTopic)
return true
}
@ -138,7 +137,7 @@ extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
Log.d(tag, "Firebase token received: \(String(describing: fcmToken))")
// We don't actually need the FCM token, since we're just using topics.
// We still print it so we can see if things were successful.
// We wait until we have a registration token before subscribing to our pollTopic
Messaging.messaging().subscribe(toTopic: pollTopic)
}
}

View file

@ -13,11 +13,6 @@ struct AppMain: App {
init() {
Log.d(tag, "Launching ntfy 🥳. Welcome!")
Log.d(tag, "Base URL is \(Config.appBaseUrl), user agent is \(ApiService.userAgent)")
// We must configure Firebase here, and not in the AppDelegate. For some reason
// configuring it there did not work.
FirebaseApp.configure()
FirebaseConfiguration.shared.setLoggerLevel(.max)
}
var body: some Scene {

View file

@ -150,6 +150,66 @@
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
},
{
"filename" : "48.png",
"idiom" : "watch",
@ -225,6 +285,13 @@
"size" : "51x51",
"subtype" : "45mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "54x54",
"subtype" : "49mm"
},
{
"filename" : "172.png",
"idiom" : "watch",
@ -256,71 +323,18 @@
"size" : "117x117",
"subtype" : "45mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "129x129",
"subtype" : "49mm"
},
{
"filename" : "1024.png",
"idiom" : "watch-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {

View file

@ -18,5 +18,7 @@
<array>
<string>remote-notification</string>
</array>
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
</dict>
</plist>

View file

@ -7,7 +7,7 @@ import Combine
class Store: ObservableObject {
static let shared = Store()
static let tag = "Store"
static let appGroup = "group.io.heckel.ntfy" // Must match app group of ntfy = ntfyNSE targets
static let appGroup = "group.com.tcaputi.ntfy" // Must match app group of ntfy = ntfyNSE targets
static let modelName = "ntfy" // Must match .xdatamodeld folder
static let prefKeyDefaultBaseUrl = "defaultBaseUrl"
@ -43,9 +43,13 @@ class Store: ObservableObject {
NotificationCenter.default
.publisher(for: .NSPersistentStoreRemoteChange)
.sink { value in
Log.d(Store.tag, "Remote change detected, refreshing view", value)
// TODO: this could probably broadcast the name of the channel
// so that only relevant views can update.
Log.d(Store.tag, "Remote change detected, refreshing views", value)
DispatchQueue.main.async {
self.hardRefresh()
NotificationCenter.default.post(name: .notificationReceived, object: nil)
}
}
.store(in: &cancellables)
@ -266,3 +270,7 @@ extension Store {
return notification
}
}
extension Foundation.Notification.Name {
static let notificationReceived = Foundation.Notification.Name("notificationReceived")
}

View file

@ -24,13 +24,21 @@ struct NotificationListView: View {
}
var body: some View {
let notificationReceived = Foundation.Notification.Name("notificationReceived")
if #available(iOS 15.0, *) {
notificationList
.refreshable {
subscriptionManager.poll(subscription)
}.onReceive(NotificationCenter.default.publisher(for: notificationReceived)) { _ in
// Handle the notification
subscriptionManager.poll(subscription)
}
} else {
notificationList
notificationList.onReceive(NotificationCenter.default.publisher(for: notificationReceived)) { _ in
// Handle the notification
subscriptionManager.poll(subscription)
}
}
}

View file

@ -15,6 +15,8 @@ struct SubscriptionListView: View {
}
var body: some View {
let notificationReceived = Foundation.Notification.Name("notificationReceived")
NavigationView {
if #available(iOS 15.0, *) {
subscriptionList
@ -22,9 +24,20 @@ struct SubscriptionListView: View {
subscriptions.forEach { subscription in
subscriptionManager.poll(subscription)
}
}.onReceive(NotificationCenter.default.publisher(for: notificationReceived)) { _ in
// Handle the notification
subscriptions.forEach { subscription in
subscriptionManager.poll(subscription)
}
}
} else {
subscriptionList
.onReceive(NotificationCenter.default.publisher(for: notificationReceived)) { _ in
// Handle the notification
subscriptions.forEach { subscription in
subscriptionManager.poll(subscription)
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {

View file

@ -6,7 +6,7 @@
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.heckel.ntfy</string>
<string>group.com.tcaputi.ntfy</string>
</array>
</dict>
</plist>

View file

@ -6,7 +6,7 @@
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.heckel.ntfy</string>
<string>group.com.tcaputi.ntfy</string>
</array>
</dict>
</plist>