From 3902fe7b30f38ec104c13087948799e38e26fa91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Fri, 6 Sep 2024 14:13:05 -0700 Subject: [PATCH 1/6] Enable push notifications feature for everyone and set notification mode to push MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit hardcodes the push notification feature flag to true, in preparation for purple testflight release. It also changes the notification mode setting string, to ensure that we won't have issues with people being stuck with local notification mode. Testing ------- Steps: 1. Run app 2. Ensure push notification flag is gone from developer Settings 3. Ensure notification mode is set to push, and that the push option is available 4. Ensure push notification settings appear as "synced successfully" Conditions: - iPhone 13 mini, iOS 17.6.1, on a device that was already under testing - iPad simulator, iOS 17.5, brand new account Changelog-Added: Push notification support Signed-off-by: Daniel D’Aquino --- damus/Models/NotificationsManager.swift | 2 +- damus/Models/PushNotificationClient.swift | 2 +- damus/Models/UserSettingsStore.swift | 9 +++++---- damus/Views/Settings/DeveloperSettingsView.swift | 4 ---- damus/Views/Settings/NotificationSettingsView.swift | 12 ++++++------ 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/damus/Models/NotificationsManager.swift b/damus/Models/NotificationsManager.swift index 2ae900fe..adbd0776 100644 --- a/damus/Models/NotificationsManager.swift +++ b/damus/Models/NotificationsManager.swift @@ -27,7 +27,7 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent, mode: UserSettingsStore.NotificationsMode) -> Bool { // Do not show notification if it's coming from a mode different from the one selected by our user - guard state.settings.notifications_mode == mode else { + guard state.settings.notification_mode == mode else { return false } diff --git a/damus/Models/PushNotificationClient.swift b/damus/Models/PushNotificationClient.swift index dc385e2c..f92d8367 100644 --- a/damus/Models/PushNotificationClient.swift +++ b/damus/Models/PushNotificationClient.swift @@ -18,7 +18,7 @@ struct PushNotificationClient { mutating func set_device_token(new_device_token: Data) async throws { self.device_token = new_device_token - if settings.enable_experimental_push_notifications && settings.notifications_mode == .push { + if settings.enable_push_notifications && settings.notification_mode == .push { try await self.send_token() } } diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift index 16b221ff..ff884aa1 100644 --- a/damus/Models/UserSettingsStore.swift +++ b/damus/Models/UserSettingsStore.swift @@ -155,8 +155,8 @@ class UserSettingsStore: ObservableObject { @Setting(key: "like_notification", default_value: true) var like_notification: Bool - @StringSetting(key: "notifications_mode", default_value: .local) - var notifications_mode: NotificationsMode + @StringSetting(key: "notification_mode", default_value: .push) + var notification_mode: NotificationsMode @Setting(key: "notification_only_from_following", default_value: false) var notification_only_from_following: Bool @@ -207,8 +207,9 @@ class UserSettingsStore: ObservableObject { @Setting(key: "always_show_onboarding_suggestions", default_value: false) var always_show_onboarding_suggestions: Bool - @Setting(key: "enable_experimental_push_notifications", default_value: false) - var enable_experimental_push_notifications: Bool + // @Setting(key: "enable_experimental_push_notifications", default_value: false) + // This was a feature flag setting during early development, but now this is enabled for everyone. + var enable_push_notifications: Bool = true @StringSetting(key: "push_notification_environment", default_value: .production) var push_notification_environment: PushNotificationClient.Environment diff --git a/damus/Views/Settings/DeveloperSettingsView.swift b/damus/Views/Settings/DeveloperSettingsView.swift index cd60b230..c3fdd982 100644 --- a/damus/Views/Settings/DeveloperSettingsView.swift +++ b/damus/Views/Settings/DeveloperSettingsView.swift @@ -18,10 +18,6 @@ struct DeveloperSettingsView: View { .toggleStyle(.switch) if settings.developer_mode { Toggle(NSLocalizedString("Always show onboarding", comment: "Developer mode setting to always show onboarding suggestions."), isOn: $settings.always_show_onboarding_suggestions) - - Toggle(NSLocalizedString("Enable experimental push notifications", comment: "Developer mode setting to enable experimental push notifications."), isOn: $settings.enable_experimental_push_notifications) - .toggleStyle(.switch) - Picker(NSLocalizedString("Push notification environment", comment: "Prompt selection of the Push notification environment (Developer feature to switch between real/production mode to test modes)."), selection: Binding( get: { () -> PushNotificationClient.Environment in diff --git a/damus/Views/Settings/NotificationSettingsView.swift b/damus/Views/Settings/NotificationSettingsView.swift index 4ce30ef4..e5185242 100644 --- a/damus/Views/Settings/NotificationSettingsView.swift +++ b/damus/Views/Settings/NotificationSettingsView.swift @@ -36,7 +36,7 @@ struct NotificationSettingsView: View { do { try await damus_state.push_notification_client.send_token() await self.sync_up_remote_notification_settings() - settings.notifications_mode = new_value + settings.notification_mode = new_value } catch { notification_mode_setting_error = String(format: NSLocalizedString("Error configuring push notifications with the server: %@", comment: "Error label shown when user tries to enable push notifications but something fails"), error.localizedDescription) @@ -47,7 +47,7 @@ struct NotificationSettingsView: View { Task { do { try await damus_state.push_notification_client.revoke_token() - settings.notifications_mode = new_value + settings.notification_mode = new_value notification_preferences_sync_state = .not_applicable } catch { @@ -67,7 +67,7 @@ struct NotificationSettingsView: View { set: { new_value in let old_value = raw_binding.wrappedValue raw_binding.wrappedValue = new_value - if self.settings.notifications_mode == .push { + if self.settings.notification_mode == .push { Task { await self.send_push_notification_preferences(on_failure: { raw_binding.wrappedValue = old_value @@ -114,7 +114,7 @@ struct NotificationSettingsView: View { var body: some View { Form { - if settings.enable_experimental_push_notifications { + if settings.enable_push_notifications { Section( header: Text("General", comment: "Section header for general damus notifications user configuration"), footer: VStack { @@ -126,7 +126,7 @@ struct NotificationSettingsView: View { ) { Picker(NSLocalizedString("Notifications mode", comment: "Prompt selection of the notification mode (Feature to switch between local notifications (generated from user's own phone) or push notifications (generated by Damus server)."), selection: Binding( - get: { settings.notifications_mode }, + get: { settings.notification_mode }, set: { newValue in self.try_to_set_notifications_mode(new_value: newValue) } @@ -194,7 +194,7 @@ struct NotificationSettingsView: View { } .onAppear(perform: { Task { - if self.settings.notifications_mode == .push { + if self.settings.notification_mode == .push { await self.sync_up_remote_notification_settings() } } From dfa72fceb10dc0cd6867870acfc1f2b69c890722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Fri, 13 Sep 2024 15:53:02 -0700 Subject: [PATCH 2/6] Fix unit test build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A change around the NostrPost interfaces caused unit tests to fail compilation. This commit fixes that. Signed-off-by: Daniel D’Aquino --- damus/Models/Post.swift | 12 +++++++----- damusTests/ReplyTests.swift | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/damus/Models/Post.swift b/damus/Models/Post.swift index acb6ac6c..cf125041 100644 --- a/damus/Models/Post.swift +++ b/damus/Models/Post.swift @@ -51,7 +51,7 @@ struct NostrPost { } /// Parse the post's contents to find more tags to apply to the final nostr event - private func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags { + func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags { var new_tags = tags for post_block in post_blocks { @@ -89,10 +89,12 @@ struct NostrPost { // MARK: - Helper structures and functions -/// A struct used for temporarily holding tag information that was parsed from a post contents to aid in building a nostr event -fileprivate struct PostTags { - let blocks: [Block] - let tags: [[String]] +extension NostrPost { + /// A struct used for temporarily holding tag information that was parsed from a post contents to aid in building a nostr event + struct PostTags { + let blocks: [Block] + let tags: [[String]] + } } func parse_post_blocks(content: String) -> [Block] { diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift index 4395a673..e2534c20 100644 --- a/damusTests/ReplyTests.swift +++ b/damusTests/ReplyTests.swift @@ -57,7 +57,8 @@ class ReplyTests: XCTestCase { let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])! let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks let post_blocks = parse_post_blocks(content: content) - let post_tags = make_post_tags(post_blocks: post_blocks, tags: []) + let post = NostrPost(content: content, kind: NostrKind.text, tags: []) + let post_tags = post.make_post_tags(post_blocks: post_blocks, tags: []) let tr = interpret_event_refs(tags: ev.tags) XCTAssertNil(tr) From 2278ab09a4864d7aca0f7f5c4ed52d918ed27190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Wed, 11 Sep 2024 15:05:03 -0700 Subject: [PATCH 3/6] Improve local contact list handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unless the user signed up after changes from Github issue #2057, the contact list delegate would never be set due to a logic error, which means latest_contact_event_changed would never get called and the app would never save a local contact list reference to pull from — which caused issues when switching to different relays. Testing -------- PASS Device: iPhone 15 simulator iOS: 17.5 Setup: Manually removed UserSettingsStore::latest_contact_event_id_hex value to replicate the entry condition for the bug Steps: 1. Add new relay (relay.zap.store) 2. Remove all other relays 3. Attempt to add relay. Ensure new relay can be added 4. Remove all relays 5. Add the `wss://notify-staging.damus.io` relay (which will not save any events) 6. Restart app 7. Try to add a new relay. Ensure a new relay can be added 8. Make a test post. Ensure the new test post is posted successfully. Changelog-Fixed: Fixed some scenarios where the contact list would never be saved locally and cause issues when switching relays. Closes: https://github.com/damus-io/damus/issues/2293 Signed-off-by: Daniel D’Aquino --- damus/Models/HomeModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift index aa375707..003a4367 100644 --- a/damus/Models/HomeModel.swift +++ b/damus/Models/HomeModel.swift @@ -127,11 +127,11 @@ class HomeModel: ContactsDelegate { /// This loads the latest contact event we have on file from NostrDB. This should be called as soon as we get the new DamusState /// Loading the latest contact list event into our `Contacts` instance from storage is important to avoid getting into weird states when the network is unreliable or when relays delete such information func load_latest_contact_event_from_damus_state() { + damus_state.contacts.delegate = self guard let latest_contact_event_id_hex = damus_state.settings.latest_contact_event_id_hex else { return } guard let latest_contact_event_id = NoteId(hex: latest_contact_event_id_hex) else { return } guard let latest_contact_event: NdbNote = damus_state.ndb.lookup_note( latest_contact_event_id)?.unsafeUnownedValue?.to_owned() else { return } process_contact_event(state: damus_state, ev: latest_contact_event) - damus_state.contacts.delegate = self } // MARK: - ContactsDelegate functions From 6fa2e8b5c635a31e765b908e94b370b0b9b88fb3 Mon Sep 17 00:00:00 2001 From: cr0bar Date: Fri, 13 Sep 2024 22:09:22 +0100 Subject: [PATCH 4/6] Fix issue where theme would be changed to black and can't be switched back on iOS 18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed line which forces preferred colour scheme to dark on iOS 18, and made adjustments to the styling to maintain text legibility Changelog-Fixed: Fixed issue where theme would be changed to black and can't be switched back on iOS 18 Closes: https://github.com/damus-io/damus/issues/2373 Co-authored-by: Daniel D’Aquino Signed-off-by: Daniel D’Aquino --- damus/Components/SupporterBadge.swift | 6 ++++-- damus/Views/NoteContentView.swift | 3 +-- damus/Views/Purple/DamusPurpleAccountView.swift | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/damus/Components/SupporterBadge.swift b/damus/Components/SupporterBadge.swift index 794bbb27..db24f066 100644 --- a/damus/Components/SupporterBadge.swift +++ b/damus/Components/SupporterBadge.swift @@ -11,11 +11,13 @@ struct SupporterBadge: View { let percent: Int? let purple_account: DamusPurple.Account? let style: Style + let text_color: Color - init(percent: Int?, purple_account: DamusPurple.Account? = nil, style: Style) { + init(percent: Int?, purple_account: DamusPurple.Account? = nil, style: Style, text_color: Color = .secondary) { self.percent = percent self.purple_account = purple_account self.style = style + self.text_color = text_color } let size: CGFloat = 17 @@ -31,7 +33,7 @@ struct SupporterBadge: View { if self.style == .full { let date = format_date(date: purple_account.created_at, time_style: .none) Text(date) - .foregroundStyle(.secondary) + .foregroundStyle(text_color) .font(.caption) } } diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift index b81cd3fb..3435c95f 100644 --- a/damus/Views/NoteContentView.swift +++ b/damus/Views/NoteContentView.swift @@ -120,8 +120,7 @@ struct NoteContentView: View { EventView(damus: damus_state, event: self.event, options: .embedded_text_only) .padding(.top) } - .background(.thinMaterial) - .preferredColorScheme(.dark) + .background(.thickMaterial) .onTapGesture(perform: { damus_state.nav.push(route: Route.Thread(thread: .init(event: self.event, damus_state: damus_state))) dismiss() diff --git a/damus/Views/Purple/DamusPurpleAccountView.swift b/damus/Views/Purple/DamusPurpleAccountView.swift index ebb9ce4e..27adae65 100644 --- a/damus/Views/Purple/DamusPurpleAccountView.swift +++ b/damus/Views/Purple/DamusPurpleAccountView.swift @@ -64,7 +64,6 @@ struct DamusPurpleAccountView: View { .padding(.bottom, 20) } .foregroundColor(.white.opacity(0.8)) - .preferredColorScheme(.dark) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) .padding() } @@ -81,7 +80,8 @@ struct DamusPurpleAccountView: View { SupporterBadge( percent: nil, purple_account: account, - style: .full + style: .full, + text_color: .white ) } } From ce63f6a96b44211585b2098173697d165d3665b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Mon, 16 Sep 2024 11:03:34 -0700 Subject: [PATCH 5/6] Make friends filter button more visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit changes the appearance of the friends filter button to make it more visible Testing: - Checked appearance in both light mode and dark mode - Checked appearance in all usages (notifications view and DM view) - Checked consistency against the filter button in Universe view Changelog-Changed: Improve visibility of friends filter button Signed-off-by: Daniel D’Aquino --- damus/Views/Buttons/FriendsButton.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/damus/Views/Buttons/FriendsButton.swift b/damus/Views/Buttons/FriendsButton.swift index b69a313d..4d44695a 100644 --- a/damus/Views/Buttons/FriendsButton.swift +++ b/damus/Views/Buttons/FriendsButton.swift @@ -28,7 +28,7 @@ struct FriendsButton: View { Image("user-added") .resizable() .frame(width: 28, height: 28) - .foregroundColor(DamusColors.adaptableGrey) + .foregroundColor(.gray) } } .buttonStyle(.plain) From 6254cea6009ad392fe93fdf255202f7753632f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Mon, 16 Sep 2024 12:21:10 -0700 Subject: [PATCH 6/6] Improve notification view filtering UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add subtitle below the toolbar title to indicate the state of the filter - Add settings icon to take user to the notification settings page, and thus make that more discoverable Testing ------- PASS Device: iPhone 13 mini iOS: 17.6.1 Coverage: 1. Switching back and forth between the notifications tab and other tabs causes subtitle to show/hide as expected in both filter options (all, friends) 2. Subtitle follows the friends filter 3. Subtitle shows after restarting the app 4. Settings icon appears and takes user to the notification setting view 5. Notification settings can be updated from that view. Changelog-Changed: Improve notification view filtering UX Closes: https://github.com/damus-io/damus/issues/2480 Signed-off-by: Daniel D’Aquino --- damus/ContentView.swift | 22 ++++++++++++++----- damus/Models/FriendFilter.swift | 13 +++++++++-- damus/Views/Buttons/FriendsButton.swift | 6 ++--- damus/Views/DirectMessagesView.swift | 2 +- .../Notifications/NotificationsView.swift | 16 ++++++++++++-- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 3f93f41b..2f52e46a 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -77,7 +77,12 @@ struct ContentView: View { @State var active_sheet: Sheets? = nil @State var damus_state: DamusState! - @SceneStorage("ContentView.selected_timeline") var selected_timeline: Timeline = .home + @State var menu_subtitle: String? = nil + @SceneStorage("ContentView.selected_timeline") var selected_timeline: Timeline = .home { + willSet { + self.menu_subtitle = nil + } + } @State var muting: MuteItem? = nil @State var confirm_mute: Bool = false @State var hide_bar: Bool = false @@ -159,9 +164,16 @@ struct ContentView: View { isSideBarOpened = false } - var timelineNavItem: Text { - return Text(timeline_name(selected_timeline)) - .bold() + var timelineNavItem: some View { + VStack { + Text(timeline_name(selected_timeline)) + .bold() + if let menu_subtitle { + Text(menu_subtitle) + .font(.caption) + .foregroundStyle(.secondary) + } + } } func MainContent(damus: DamusState) -> some View { @@ -180,7 +192,7 @@ struct ContentView: View { PostingTimelineView case .notifications: - NotificationsView(state: damus, notifications: home.notifications) + NotificationsView(state: damus, notifications: home.notifications, subtitle: $menu_subtitle) case .dms: DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings) diff --git a/damus/Models/FriendFilter.swift b/damus/Models/FriendFilter.swift index b2a15544..fd0399e9 100644 --- a/damus/Models/FriendFilter.swift +++ b/damus/Models/FriendFilter.swift @@ -9,7 +9,7 @@ import Foundation enum FriendFilter: String, StringCodable { case all - case friends + case friends_of_friends init?(from string: String) { guard let ff = FriendFilter(rawValue: string) else { @@ -27,8 +27,17 @@ enum FriendFilter: String, StringCodable { switch self { case .all: return true - case .friends: + case .friends_of_friends: return contacts.is_in_friendosphere(pubkey) } } + + func description() -> String { + switch self { + case .all: + return NSLocalizedString("All", comment: "Human-readable short description of the 'friends filter' when it is set to 'all'") + case .friends_of_friends: + return NSLocalizedString("Friends of friends", comment: "Human-readable short description of the 'friends filter' when it is set to 'friends-of-friends'") + } + } } diff --git a/damus/Views/Buttons/FriendsButton.swift b/damus/Views/Buttons/FriendsButton.swift index 4d44695a..97862747 100644 --- a/damus/Views/Buttons/FriendsButton.swift +++ b/damus/Views/Buttons/FriendsButton.swift @@ -14,12 +14,12 @@ struct FriendsButton: View { Button(action: { switch self.filter { case .all: - self.filter = .friends - case .friends: + self.filter = .friends_of_friends + case .friends_of_friends: self.filter = .all } }) { - if filter == .friends { + if filter == .friends_of_friends { LINEAR_GRADIENT .mask(Image("user-added") .resizable() diff --git a/damus/Views/DirectMessagesView.swift b/damus/Views/DirectMessagesView.swift index 0adf2cf9..dff8d08c 100644 --- a/damus/Views/DirectMessagesView.swift +++ b/damus/Views/DirectMessagesView.swift @@ -103,7 +103,7 @@ struct DirectMessagesView: View { func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageModel]) -> Bool { for dm in dms { - if !FriendFilter.friends.filter(contacts: contacts, pubkey: dm.pubkey) { + if !FriendFilter.friends_of_friends.filter(contacts: contacts, pubkey: dm.pubkey) { return true } } diff --git a/damus/Views/Notifications/NotificationsView.swift b/damus/Views/Notifications/NotificationsView.swift index 2f824d18..1168ae33 100644 --- a/damus/Views/Notifications/NotificationsView.swift +++ b/damus/Views/Notifications/NotificationsView.swift @@ -56,6 +56,7 @@ struct NotificationsView: View { @ObservedObject var notifications: NotificationsModel @StateObject var filter = NotificationFilter() @SceneStorage("NotificationsView.filter_state") var filter_state: NotificationFilterState = .all + @Binding var subtitle: String? @Environment(\.colorScheme) var colorScheme @@ -99,6 +100,15 @@ struct NotificationsView: View { .tag(NotificationFilterState.replies) } .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button( + action: { state.nav.push(route: Route.NotificationSettings(settings: state.settings)) }, + label: { + Image("settings") + .foregroundColor(.gray) + } + ) + } ToolbarItem(placement: .navigationBarTrailing) { if would_filter_non_friends_from_notifications(contacts: state.contacts, state: filter_state, items: self.notifications.notifications) { FriendsButton(filter: $filter.fine_filter) @@ -107,12 +117,14 @@ struct NotificationsView: View { } .onChange(of: filter.fine_filter) { val in state.settings.friend_filter = val + self.subtitle = filter.fine_filter.description() } .onChange(of: filter_state) { val in filter.state = val } .onAppear { self.filter.fine_filter = state.settings.friend_filter + self.subtitle = filter.fine_filter.description() filter.state = filter_state } .safeAreaInset(edge: .top, spacing: 0) { @@ -163,7 +175,7 @@ struct NotificationsView: View { struct NotificationsView_Previews: PreviewProvider { static var previews: some View { - NotificationsView(state: test_damus_state, notifications: NotificationsModel(), filter: NotificationFilter()) + NotificationsView(state: test_damus_state, notifications: NotificationsModel(), filter: NotificationFilter(), subtitle: .constant(nil)) } } @@ -174,7 +186,7 @@ func would_filter_non_friends_from_notifications(contacts: Contacts, state: Noti continue } - if item.would_filter({ ev in FriendFilter.friends.filter(contacts: contacts, pubkey: ev.pubkey) }) { + if item.would_filter({ ev in FriendFilter.friends_of_friends.filter(contacts: contacts, pubkey: ev.pubkey) }) { return true } }