diff --git a/ntfy.xcodeproj/project.pbxproj b/ntfy.xcodeproj/project.pbxproj index 5526708..8aff12f 100644 --- a/ntfy.xcodeproj/project.pbxproj +++ b/ntfy.xcodeproj/project.pbxproj @@ -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; diff --git a/ntfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ntfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a445f0c..e1ec2d0 100644 --- a/ntfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ntfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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" } }, { diff --git a/ntfy.xcodeproj/xcshareddata/xcschemes/ntfy.xcscheme b/ntfy.xcodeproj/xcshareddata/xcschemes/ntfy.xcscheme new file mode 100644 index 0000000..5967b20 --- /dev/null +++ b/ntfy.xcodeproj/xcshareddata/xcschemes/ntfy.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ntfy.xcodeproj/xcshareddata/xcschemes/ntfyNSE.xcscheme b/ntfy.xcodeproj/xcshareddata/xcschemes/ntfyNSE.xcscheme new file mode 100644 index 0000000..7a4f970 --- /dev/null +++ b/ntfy.xcodeproj/xcshareddata/xcschemes/ntfyNSE.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ntfy/App/AppDelegate.swift b/ntfy/App/AppDelegate.swift index c0265a5..dcb5551 100644 --- a/ntfy/App/AppDelegate.swift +++ b/ntfy/App/AppDelegate.swift @@ -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) @@ -28,13 +33,7 @@ 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 } @@ -137,8 +136,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate { 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) } } diff --git a/ntfy/App/AppMain.swift b/ntfy/App/AppMain.swift index b753e28..1fedf39 100644 --- a/ntfy/App/AppMain.swift +++ b/ntfy/App/AppMain.swift @@ -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 { diff --git a/ntfy/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json b/ntfy/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json index 04de9d4..f78687a 100644 --- a/ntfy/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ntfy/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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" : { diff --git a/ntfy/Info.plist b/ntfy/Info.plist index f1f6093..6abed55 100644 --- a/ntfy/Info.plist +++ b/ntfy/Info.plist @@ -18,5 +18,7 @@ remote-notification + FirebaseAppDelegateProxyEnabled + diff --git a/ntfy/Persistence/Store.swift b/ntfy/Persistence/Store.swift index 03eab1b..7b40d33 100644 --- a/ntfy/Persistence/Store.swift +++ b/ntfy/Persistence/Store.swift @@ -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") +} diff --git a/ntfy/Views/NotificationListView.swift b/ntfy/Views/NotificationListView.swift index 48b3149..4f3815b 100644 --- a/ntfy/Views/NotificationListView.swift +++ b/ntfy/Views/NotificationListView.swift @@ -22,15 +22,23 @@ struct NotificationListView: View { private var subscriptionManager: SubscriptionManager { return SubscriptionManager(store: store) } - + 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) + } } } diff --git a/ntfy/Views/SubscriptionListView.swift b/ntfy/Views/SubscriptionListView.swift index 537b727..5bcd4bc 100644 --- a/ntfy/Views/SubscriptionListView.swift +++ b/ntfy/Views/SubscriptionListView.swift @@ -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 { diff --git a/ntfy/ntfy.entitlements b/ntfy/ntfy.entitlements index 384fbad..d16d692 100644 --- a/ntfy/ntfy.entitlements +++ b/ntfy/ntfy.entitlements @@ -6,7 +6,7 @@ development com.apple.security.application-groups - group.io.heckel.ntfy + group.com.tcaputi.ntfy diff --git a/ntfyNSE/ntfyNSE.entitlements b/ntfyNSE/ntfyNSE.entitlements index 384fbad..d16d692 100644 --- a/ntfyNSE/ntfyNSE.entitlements +++ b/ntfyNSE/ntfyNSE.entitlements @@ -6,7 +6,7 @@ development com.apple.security.application-groups - group.io.heckel.ntfy + group.com.tcaputi.ntfy