From f253b681c6205e0a17fa99b2266d46b44763688a Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 30 Sep 2024 17:49:27 -0500 Subject: [PATCH 1/4] notifications: allow group invites to come in as a push notificationon android, ios wip --- .../java/io/tlon/landscape/api/TalkApi.java | 4 ++ .../java/io/tlon/landscape/models/Yarn.java | 22 ++++++--- .../TalkNotificationManager.java | 49 ++++++++++++++++--- desk/lib/activity.hoon | 4 +- desk/lib/notify.hoon | 17 ++++--- 5 files changed, 72 insertions(+), 24 deletions(-) diff --git a/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/api/TalkApi.java b/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/api/TalkApi.java index cbe7272d05..987acab76d 100644 --- a/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/api/TalkApi.java +++ b/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/api/TalkApi.java @@ -137,6 +137,10 @@ public void fetchGroups(TalkObjectCallback callback) { fetchObject("/~/scry/groups/groups/light", callback); } + public void fetchGangs(TalkObjectCallback callback) { + fetchObject("/~/scry/groups/gangs", callback); + } + public void fetchGroupChannel(String channelId, TalkObjectCallback callback) { fetchGroups(new TalkObjectCallback() { @Override diff --git a/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/models/Yarn.java b/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/models/Yarn.java index c898e0e725..78afb365fd 100644 --- a/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/models/Yarn.java +++ b/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/models/Yarn.java @@ -5,6 +5,7 @@ import org.json.JSONObject; import java.util.ArrayList; +import java.util.Optional; public class Yarn { @@ -12,7 +13,8 @@ public class Yarn { public Boolean isGroup; public Boolean isClub; public String wer; - public String channelId; + public Optional channelId; + public Optional groupId; public String senderId; public String contentText; @@ -62,18 +64,22 @@ public Yarn(JSONObject yarnObject) throws JSONException { isGroup = !rope.isNull("group"); isValidNotification = rope.getString("desk").equals("groups") && - !thread.endsWith("/channel/edit") && + (!thread.endsWith("/channel/edit") && !thread.endsWith("/channel/add") && !thread.endsWith("/channel/del") && !thread.endsWith("/joins") && - !thread.endsWith("/leaves") && - (!rope.isNull("channel") || !isGroup); + !thread.endsWith("/leaves") || + !isGroup); wer = yarnObject.getString("wer"); - channelId = isGroup ? rope.getString("channel") : thread.replace("/dm/", "").replace("/club/", ""); - isClub = channelId.startsWith("0v"); + groupId = !isGroup ? Optional.empty() : + Optional.of(rope.getString("group")); + channelId = isGroup ? + rope.isNull("channel") ? Optional.empty() : Optional.of(rope.getString("channel")) + : Optional.of(thread.replace("/dm/", "").replace("/club/", "")); + isClub = channelId.isPresent() && channelId.get().startsWith("0v"); contentText = String.join("", parts); - if (senderId == null && channelId.startsWith("~")) { - senderId = channelId; + if (senderId == null && channelId.isPresent() && channelId.get().startsWith("~")) { + senderId = channelId.get(); } } diff --git a/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/notifications/TalkNotificationManager.java b/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/notifications/TalkNotificationManager.java index 2cb7b8f286..347d4ffad9 100644 --- a/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/notifications/TalkNotificationManager.java +++ b/apps/tlon-mobile/android/app/src/main/java/io/tlon/landscape/notifications/TalkNotificationManager.java @@ -18,6 +18,7 @@ import org.json.JSONObject; import java.util.Date; +import java.util.Iterator; import io.invertase.firebase.crashlytics.ReactNativeFirebaseCrashlyticsNativeHelper; import io.tlon.landscape.AppLifecycleManager; @@ -75,10 +76,11 @@ public void onComplete(JSONObject response) { @Override public void onComplete(JSONObject response) { final Contact contact = new Contact(yarn.senderId, response); + final String channelId = yarn.channelId.orElse(""); createNotificationTitle(api, yarn, contact, title -> { Bundle data = new Bundle(); data.putString("wer", yarn.wer); - data.putString("channelId", yarn.channelId); + data.putString("channelId", channelId); data.putInt("notificationId", id); sendNotification( context, @@ -109,9 +111,9 @@ public void onError(VolleyError error) { } private static void createNotificationTitle(TalkApi api, Yarn yarn, Contact contact, TalkNotificationContentCallback callback) { - if (yarn.isGroup) { - final String fallbackTitle = yarn.channelId.replace("chat/", ""); - api.fetchGroupChannel(yarn.channelId, new TalkObjectCallback() { + if (yarn.isGroup && yarn.channelId.isPresent()) { + final String fallbackTitle = yarn.channelId.get().replace("chat/", ""); + api.fetchGroupChannel(yarn.channelId.get(), new TalkObjectCallback() { @Override public void onComplete(JSONObject response) { try { @@ -131,18 +133,49 @@ public void onError(VolleyError error) { return; } - if (yarn.isClub) { - api.fetchClub(yarn.channelId, new TalkObjectCallback() { + if (yarn.isGroup && yarn.groupId.isPresent() && !yarn.channelId.isPresent()) { + // this only works for group invites, if we start handling other events, we need to + // also fetch groups and prioritize that metadata over gangs + api.fetchGangs(new TalkObjectCallback() { + final String groupId = yarn.groupId.get(); @Override public void onComplete(JSONObject response) { - Club club = new Club(yarn.channelId, response); + try { + JSONObject group = response.getJSONObject(groupId); + if (group.isNull("preview")) { + callback.onComplete(groupId); + return; + } + + String title = group.getJSONObject("preview").getJSONObject("meta").getString("title"); + callback.onComplete(title.isEmpty() ? groupId : title); + } catch (JSONException e) { + callback.onComplete(groupId); + } + } + + @Override + public void onError(VolleyError error) { + ReactNativeFirebaseCrashlyticsNativeHelper.recordNativeException(new Exception("notifications_fetch_groups", error)); + callback.onComplete(groupId); + } + }); + + return; + } + + if (yarn.isClub && yarn.channelId.isPresent()) { + api.fetchClub(yarn.channelId.get(), new TalkObjectCallback() { + @Override + public void onComplete(JSONObject response) { + Club club = new Club(yarn.channelId.get(), response); callback.onComplete(club.displayName); } @Override public void onError(VolleyError error) { ReactNativeFirebaseCrashlyticsNativeHelper.recordNativeException(new Exception("notifications_fetch_club", error)); - callback.onComplete(yarn.channelId); + callback.onComplete(yarn.channelId.get()); } }); return; diff --git a/desk/lib/activity.hoon b/desk/lib/activity.hoon index f0caa8496c..c1d17645d8 100644 --- a/desk/lib/activity.hoon +++ b/desk/lib/activity.hoon @@ -168,10 +168,12 @@ ?: ?=(%none allowed) | =/ type (event-type incoming-event) ?+ type | + %post & %reply & - %dm-invite & %dm-post & %dm-reply & + %dm-invite & + %group-invite & %post-mention & %reply-mention & %dm-post-mention & diff --git a/desk/lib/notify.hoon b/desk/lib/notify.hoon index 3ca9dfd301..9c5805abcc 100644 --- a/desk/lib/notify.hoon +++ b/desk/lib/notify.hoon @@ -124,7 +124,16 @@ (flatten:cu content.event) == :: - ?(%group-ask %group-invite %group-join %group-kick %group-role) + %group-invite + =+ .^(=gangs:g:a %gx /(scot %p our)/groups/(scot %da now)/gangs/noun) + :~ [%ship ship.event] + ' sent you an invite to ' + ?~ gang=(~(get by gangs) group.event) 'a group' + ?~ pev.u.gang 'a group' + [%emph title.meta.u.pev.u.gang] + == + :: + ?(%group-ask %group-join %group-kick %group-role) =+ .^ =group:g:a %gx (scot %p our) %groups @@ -137,12 +146,6 @@ ' has requested to join ' [%emph title.meta.group] == - :: - %group-invite - :~ [%ship ship.event] - ' sent you an invite to ' - [%emph title.meta.group] - == :: %group-join :~ [%ship ship.event] From 2f3b318b646da13cee3af2194d6e28ccdec74a8a Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 1 Oct 2024 13:21:13 -0500 Subject: [PATCH 2/4] invites: working background notifications in iOS, push always falls back --- .../ios/Landscape.xcodeproj/project.pbxproj | 16 ++++++- .../tlon-mobile/ios/Shared/Models/Group.swift | 8 ++++ .../ios/Shared/Models/TlonError.swift | 1 + apps/tlon-mobile/ios/Shared/Models/Yarn.swift | 46 +++++++++++++------ .../ios/Shared/Networking/PocketChatAPI.swift | 4 ++ .../PushNotificationManager.swift | 2 +- .../ios/Shared/Storage/GroupStore.swift | 12 +++++ 7 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 apps/tlon-mobile/ios/Shared/Storage/GroupStore.swift diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index 559c2a30ad..7d6ce95bf7 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -12,6 +12,11 @@ 3DC46BC1B669DF8F5F059CA8 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACB38DC4FF4D6377774BF5F /* ExpoModulesProvider.swift */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; 4ACCD7281520ED84DDC2759A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 74D2BEF1344CF71A07C17F5E /* PrivacyInfo.xcprivacy */; }; + 5A1EB9532CAC451A00029EDC /* GroupStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1EB9522CAC451A00029EDC /* GroupStore.swift */; }; + 5A1EB9542CAC451A00029EDC /* GroupStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1EB9522CAC451A00029EDC /* GroupStore.swift */; }; + 5A1EB9552CAC451A00029EDC /* GroupStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1EB9522CAC451A00029EDC /* GroupStore.swift */; }; + 5A1EB9562CAC451A00029EDC /* GroupStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1EB9522CAC451A00029EDC /* GroupStore.swift */; }; + 5A1EB9572CAC451A00029EDC /* GroupStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1EB9522CAC451A00029EDC /* GroupStore.swift */; }; 630DE0B72C51A0F80053603B /* Error+isAFTimeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632793BD2C4AE4B500F942B1 /* Error+isAFTimeout.swift */; }; 630DE0C52C51A8780053603B /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D386702A60A3E600AFB46E /* UIColor+Extension.swift */; }; 630DE0C62C51A8780053603B /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 700B635C2A71DFE90017F40F /* Contact.swift */; }; @@ -225,6 +230,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Landscape/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Landscape/main.m; sourceTree = ""; }; 3B0B210E999A7E0E1A345468 /* Pods-Landscape-preview.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Landscape-preview.release.xcconfig"; path = "Target Support Files/Pods-Landscape-preview/Pods-Landscape-preview.release.xcconfig"; sourceTree = ""; }; + 5A1EB9522CAC451A00029EDC /* GroupStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStore.swift; sourceTree = ""; }; 630DE0B82C51A1500053603B /* LandscapePreview-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "LandscapePreview-Bridging-Header.h"; path = "Landscape/LandscapePreview-Bridging-Header.h"; sourceTree = ""; }; 630DE0E22C51A8790053603B /* Notifications-preview.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notifications-preview.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 630DE0EB2C51AC840053603B /* UserDefaults+appGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+appGroup.swift"; sourceTree = ""; }; @@ -269,11 +275,11 @@ 70F99A8E2B2D2B5700D77256 /* LandscapeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LandscapeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 70F99A972B2D2B6E00D77256 /* YarnTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YarnTests.swift; sourceTree = ""; }; 74633B6584F10A6AA2FEE339 /* Pods-Landscape.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Landscape.release.xcconfig"; path = "Target Support Files/Pods-Landscape/Pods-Landscape.release.xcconfig"; sourceTree = ""; }; - 74D2BEF1344CF71A07C17F5E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Landscape/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 74D2BEF1344CF71A07C17F5E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Landscape/PrivacyInfo.xcprivacy; sourceTree = ""; }; 79BDD5DCE15A385BB361AED1 /* Pods_Landscape.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Landscape.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7A4D352CD337FB3A3BF06240 /* Pods-Landscape.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Landscape.release.xcconfig"; path = "Target Support Files/Pods-Landscape/Pods-Landscape.release.xcconfig"; sourceTree = ""; }; 8ACB38DC4FF4D6377774BF5F /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Landscape-preview/ExpoModulesProvider.swift"; sourceTree = ""; }; - 9D6B23EFDEDD5888235A22BB /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Landscape/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 9D6B23EFDEDD5888235A22BB /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Landscape/PrivacyInfo.xcprivacy; sourceTree = ""; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Landscape/SplashScreen.storyboard; sourceTree = ""; }; B35AC6CF758CAFC9D112A69F /* Pods-Landscape.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Landscape.debug.xcconfig"; path = "Target Support Files/Pods-Landscape/Pods-Landscape.debug.xcconfig"; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; @@ -438,6 +444,7 @@ 6374ACF82C4ACD5000E637C0 /* LoginStore.swift */, 700B635F2A71E0810017F40F /* SettingsStore.swift */, 7036E3532ACD0FC90020A9FB /* UserDefaultsStore.swift */, + 5A1EB9522CAC451A00029EDC /* GroupStore.swift */, ); path = Storage; sourceTree = ""; @@ -1180,6 +1187,7 @@ 7083A16C2AFB01A30022404A /* TlonError.swift in Sources */, 632793BE2C4AE4B500F942B1 /* Error+isAFTimeout.swift in Sources */, 7036E3542ACD0FC90020A9FB /* UserDefaultsStore.swift in Sources */, + 5A1EB9532CAC451A00029EDC /* GroupStore.swift in Sources */, 70D3865A2A609BFC00AFB46E /* PocketNotificationsAPI.swift in Sources */, C83014822C7BA74C00D9A5CA /* UIFont+SystemDesign.m in Sources */, 70D3865B2A609BFC00AFB46E /* PocketAPI.swift in Sources */, @@ -1226,6 +1234,7 @@ 63E27E0E2C5AF26C008ACB45 /* Alamofire+sessionWithSharedCookieStorage.swift in Sources */, 630DE0CF2C51A8780053603B /* PocketUserAPI.swift in Sources */, 630DE0D02C51A8780053603B /* UserDefaultsStore.swift in Sources */, + 5A1EB9572CAC451A00029EDC /* GroupStore.swift in Sources */, 630DE0D12C51A8780053603B /* ContactStore.swift in Sources */, 630DE0D22C51A8780053603B /* NotificationService.swift in Sources */, 630DE0D32C51A8780053603B /* ClubStore.swift in Sources */, @@ -1257,6 +1266,7 @@ 63E27E0D2C5AF26C008ACB45 /* Alamofire+sessionWithSharedCookieStorage.swift in Sources */, 632793C62C4AE56F00F942B1 /* PocketUserAPI.swift in Sources */, 632793C42C4AE53A00F942B1 /* UserDefaultsStore.swift in Sources */, + 5A1EB9562CAC451A00029EDC /* GroupStore.swift in Sources */, 632793C32C4AE53500F942B1 /* ContactStore.swift in Sources */, 6374ACE22C49DA9600E637C0 /* NotificationService.swift in Sources */, 632793CB2C4AE7D900F942B1 /* ClubStore.swift in Sources */, @@ -1280,6 +1290,7 @@ 70DBBFEC2B7C60B50021EA96 /* TlonError.swift in Sources */, 632793BF2C4AE4B500F942B1 /* Error+isAFTimeout.swift in Sources */, 70DBBFED2B7C60B50021EA96 /* UserDefaultsStore.swift in Sources */, + 5A1EB9542CAC451A00029EDC /* GroupStore.swift in Sources */, 70DBBFEE2B7C60B50021EA96 /* PocketNotificationsAPI.swift in Sources */, 70DBBFEF2B7C60B50021EA96 /* PocketAPI.swift in Sources */, 70DBBFF02B7C60B50021EA96 /* PushNotificationManager.swift in Sources */, @@ -1326,6 +1337,7 @@ 70F99AA92B2D337600D77256 /* GroupChannelStore.swift in Sources */, 70F99A9F2B2D336800D77256 /* Group.swift in Sources */, 70F99AA32B2D336E00D77256 /* PocketUserAPI.swift in Sources */, + 5A1EB9552CAC451A00029EDC /* GroupStore.swift in Sources */, 70F99AA22B2D336E00D77256 /* UrbitAPI.swift in Sources */, 70F99AAA2B2D337600D77256 /* SettingsStore.swift in Sources */, 70F99AA62B2D336E00D77256 /* PocketChatAPI.swift in Sources */, diff --git a/apps/tlon-mobile/ios/Shared/Models/Group.swift b/apps/tlon-mobile/ios/Shared/Models/Group.swift index 5723cc95df..5fe79b532e 100644 --- a/apps/tlon-mobile/ios/Shared/Models/Group.swift +++ b/apps/tlon-mobile/ios/Shared/Models/Group.swift @@ -21,3 +21,11 @@ struct Group: Codable { let channels: [String: GroupChannel] let meta: GroupMeta } + +struct Gang: Codable { + let preview: GangPreview? +} + +struct GangPreview: Codable { + let meta: GroupMeta +} diff --git a/apps/tlon-mobile/ios/Shared/Models/TlonError.swift b/apps/tlon-mobile/ios/Shared/Models/TlonError.swift index 444c9bb28e..f82e818b71 100644 --- a/apps/tlon-mobile/ios/Shared/Models/TlonError.swift +++ b/apps/tlon-mobile/ios/Shared/Models/TlonError.swift @@ -14,4 +14,5 @@ enum TlonError { static var NotificationsFetchContacts = "notifications_fetch_contacts" static var NotificationsShowBanner = "notifications_show_banner" static var NotificationsShowFallback = "notifications_show_fallback" + static var NotificationsFetchGangs = "notifications_fetch_gangs" } diff --git a/apps/tlon-mobile/ios/Shared/Models/Yarn.swift b/apps/tlon-mobile/ios/Shared/Models/Yarn.swift index 6520d9d77a..586acfe395 100644 --- a/apps/tlon-mobile/ios/Shared/Models/Yarn.swift +++ b/apps/tlon-mobile/ios/Shared/Models/Yarn.swift @@ -18,12 +18,11 @@ struct Yarn: Decodable { // Flag group invitations and group-meta events as invalid var isValidNotification: Bool { (rope.desk == "groups") && - !rope.thread.hasSuffix("/channel/edit") && + (!rope.thread.hasSuffix("/channel/edit") && !rope.thread.hasSuffix("/channel/add") && !rope.thread.hasSuffix("/channel/del") && !rope.thread.hasSuffix("/joins") && - !rope.thread.hasSuffix("/leaves") && - (rope.channel != nil || !isGroup) + !rope.thread.hasSuffix("/leaves") || !isGroup) } var isGroup: Bool { @@ -31,7 +30,7 @@ struct Yarn: Decodable { } var isClub: Bool { - channelID.starts(with: "0v") + (channelID ?? "").starts(with: "0v") } var isInvitation: Bool { @@ -42,13 +41,21 @@ struct Yarn: Decodable { isInvitation ? .invitation : .message } - var channelID: String { + var channelID: String? { + if isGroup && rope.channel == nil { + return nil + } + if isGroup, let channel = rope.channel { return channel } return rope.thread.replacingOccurrences(of: "/dm/", with: "").replacingOccurrences(of: "/club/", with: "") } + + var groupID: String? { + return rope.group + } var senderShipName: String? { for item in con { @@ -109,25 +116,34 @@ struct Yarn: Decodable { return "" } - var displayName: String? + var displayName: String? = channelID ?? groupID - if isGroup { + if let channelID { + if isGroup { do { - let groupChannel = try await GroupChannelStore.sharedInstance.getOrFetchItem(channelID) - displayName = groupChannel?.meta.title + let groupChannel = try await GroupChannelStore.sharedInstance.getOrFetchItem(channelID) + displayName = groupChannel?.meta.title } catch { - error.logWithDomain(TlonError.NotificationsFetchGroupChannel) + error.logWithDomain(TlonError.NotificationsFetchGroupChannel) } - } else { + } else { + do { + let club = try await ClubStore.sharedInstance.getOrFetchItem(channelID) + displayName = club?.displayName + } catch { + error.logWithDomain(TlonError.NotificationsFetchClub) + } + } + } else if let groupID { do { - let club = try await ClubStore.sharedInstance.getOrFetchItem(channelID) - displayName = club?.displayName + let group = try await GroupStore.sharedInstance.getOrFetchItem(groupID) + displayName = group?.preview?.meta.title } catch { - error.logWithDomain(TlonError.NotificationsFetchClub) + error.logWithDomain(TlonError.NotificationsFetchGangs) } } - return displayName ?? channelID + return displayName ?? "" } } diff --git a/apps/tlon-mobile/ios/Shared/Networking/PocketChatAPI.swift b/apps/tlon-mobile/ios/Shared/Networking/PocketChatAPI.swift index 4b559bca79..9bd225dc81 100644 --- a/apps/tlon-mobile/ios/Shared/Networking/PocketChatAPI.swift +++ b/apps/tlon-mobile/ios/Shared/Networking/PocketChatAPI.swift @@ -16,6 +16,10 @@ extension PocketAPI { func fetchGroups() async throws -> [String: Group] { try await fetchDecodable("/~/scry/groups/groups/light") } + + func fetchGangs() async throws -> [String: Gang] { + try await fetchDecodable("/~/scry/groups/gangs") + } func fetchGroupChannels() async throws -> [String: GroupChannel] { let groups = try await fetchGroups() diff --git a/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift b/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift index db2e52fe48..fc95311dfe 100644 --- a/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift +++ b/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift @@ -135,7 +135,7 @@ enum NotificationCategory: String { content: UNMutableNotificationContent = UNMutableNotificationContent() ) async -> (UNNotificationContent, INSendMessageIntent?) { content.interruptionLevel = .active - content.threadIdentifier = yarn.channelID + content.threadIdentifier = yarn.rope.thread content.title = await yarn.getTitle() content.body = yarn.body content.categoryIdentifier = yarn.category.rawValue diff --git a/apps/tlon-mobile/ios/Shared/Storage/GroupStore.swift b/apps/tlon-mobile/ios/Shared/Storage/GroupStore.swift new file mode 100644 index 0000000000..f7bf13989a --- /dev/null +++ b/apps/tlon-mobile/ios/Shared/Storage/GroupStore.swift @@ -0,0 +1,12 @@ +// +// GroupStore.swift +// Landscape +// +// Created by Hunter Miller on 10/1/24. +// + +import Foundation + +final class GroupStore: UserDefaultsStore { + static let sharedInstance = GroupStore(storageKey: "store.groups", fetchItems: PocketAPI.shared.fetchGangs) +} From dafa2803674828559a64287ef942a85cb3ca6a32 Mon Sep 17 00:00:00 2001 From: David Lee Date: Tue, 1 Oct 2024 12:52:04 -0700 Subject: [PATCH 3/4] Match NSE min iOS version to host min iOS version --- apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index 7d6ce95bf7..0758dac6f3 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -1479,7 +1479,7 @@ INFOPLIST_FILE = "Notifications/Info-preview.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Notifications-preview"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1524,7 +1524,7 @@ INFOPLIST_FILE = "Notifications/Info-preview.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Notifications-preview"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1570,7 +1570,7 @@ INFOPLIST_FILE = Notifications/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Notifications; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1615,7 +1615,7 @@ INFOPLIST_FILE = Notifications/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Notifications; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From dfcb9c2dbb1fa936a9f7b1ee2dd5831e6bcbf27f Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 1 Oct 2024 15:15:20 -0500 Subject: [PATCH 4/4] notifications: fix ios compat for push service and remove background notif handling --- .../ios/Landscape.xcodeproj/project.pbxproj | 4 ++-- apps/tlon-mobile/ios/Landscape/AppDelegate.mm | 18 ++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index 7d6ce95bf7..e0a0e5459b 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -1479,7 +1479,7 @@ INFOPLIST_FILE = "Notifications/Info-preview.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Notifications-preview"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1524,7 +1524,7 @@ INFOPLIST_FILE = "Notifications/Info-preview.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Notifications-preview"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/apps/tlon-mobile/ios/Landscape/AppDelegate.mm b/apps/tlon-mobile/ios/Landscape/AppDelegate.mm index e33a4b43f8..cab55584cd 100644 --- a/apps/tlon-mobile/ios/Landscape/AppDelegate.mm +++ b/apps/tlon-mobile/ios/Landscape/AppDelegate.mm @@ -17,15 +17,15 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // You can add your custom initial props in the dictionary below. // They will be passed down to the ViewController used by React Native. self.initialProps = @{}; - + [PushNotificationManager configure]; - + #if PREVIEW [RNBranch useTestInstance]; #endif - + [RNBranch initSessionWithLaunchOptions:launchOptions isReferrable:YES]; - + // Listen to changes in app-specific cookie storage, and push those to the app group shared // storage. // Ideally, we'd exclusively use the app group storage for all cookie read/writes, but I could @@ -60,7 +60,7 @@ - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(N if ([RNBranch application:application openURL:url options:options]) { return YES; } - + return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; } @@ -70,7 +70,7 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull N if ([RNBranch continueUserActivity:userActivity]) { return YES; } - + BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; } @@ -87,10 +87,4 @@ - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotif return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; } -// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler -{ - return [PushNotificationManager handleBackgroundNotification:userInfo completionHandler:completionHandler]; -} - @end