Merge branch 'release_1.10'

This commit is contained in:
Daniel D’Aquino
2024-09-18 19:14:07 -07:00
15 changed files with 76 additions and 42 deletions

View File

@@ -11,11 +11,13 @@ struct SupporterBadge: View {
let percent: Int? let percent: Int?
let purple_account: DamusPurple.Account? let purple_account: DamusPurple.Account?
let style: Style 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.percent = percent
self.purple_account = purple_account self.purple_account = purple_account
self.style = style self.style = style
self.text_color = text_color
} }
let size: CGFloat = 17 let size: CGFloat = 17
@@ -31,7 +33,7 @@ struct SupporterBadge: View {
if self.style == .full { if self.style == .full {
let date = format_date(date: purple_account.created_at, time_style: .none) let date = format_date(date: purple_account.created_at, time_style: .none)
Text(date) Text(date)
.foregroundStyle(.secondary) .foregroundStyle(text_color)
.font(.caption) .font(.caption)
} }
} }

View File

@@ -77,7 +77,12 @@ struct ContentView: View {
@State var active_sheet: Sheets? = nil @State var active_sheet: Sheets? = nil
@State var damus_state: DamusState! @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 muting: MuteItem? = nil
@State var confirm_mute: Bool = false @State var confirm_mute: Bool = false
@State var hide_bar: Bool = false @State var hide_bar: Bool = false
@@ -101,9 +106,16 @@ struct ContentView: View {
isSideBarOpened = false isSideBarOpened = false
} }
var timelineNavItem: Text { var timelineNavItem: some View {
return Text(timeline_name(selected_timeline)) VStack {
.bold() Text(timeline_name(selected_timeline))
.bold()
if let menu_subtitle {
Text(menu_subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
}
} }
func MainContent(damus: DamusState) -> some View { func MainContent(damus: DamusState) -> some View {
@@ -122,7 +134,7 @@ struct ContentView: View {
PostingTimelineView(damus_state: damus_state!, home: home, active_sheet: $active_sheet) PostingTimelineView(damus_state: damus_state!, home: home, active_sheet: $active_sheet)
case .notifications: case .notifications:
NotificationsView(state: damus, notifications: home.notifications) NotificationsView(state: damus, notifications: home.notifications, subtitle: $menu_subtitle)
case .dms: case .dms:
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings) DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings)

View File

@@ -9,7 +9,7 @@ import Foundation
enum FriendFilter: String, StringCodable { enum FriendFilter: String, StringCodable {
case all case all
case friends case friends_of_friends
init?(from string: String) { init?(from string: String) {
guard let ff = FriendFilter(rawValue: string) else { guard let ff = FriendFilter(rawValue: string) else {
@@ -27,8 +27,17 @@ enum FriendFilter: String, StringCodable {
switch self { switch self {
case .all: case .all:
return true return true
case .friends: case .friends_of_friends:
return contacts.is_in_friendosphere(pubkey) 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'")
}
}
} }

View File

@@ -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 { 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 // 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 return false
} }

View File

@@ -51,7 +51,7 @@ struct NostrPost {
} }
/// Parse the post's contents to find more tags to apply to the final nostr event /// 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 var new_tags = tags
for post_block in post_blocks { for post_block in post_blocks {
@@ -89,10 +89,12 @@ struct NostrPost {
// MARK: - Helper structures and functions // 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 extension NostrPost {
fileprivate struct PostTags { /// A struct used for temporarily holding tag information that was parsed from a post contents to aid in building a nostr event
let blocks: [Block] struct PostTags {
let tags: [[String]] let blocks: [Block]
let tags: [[String]]
}
} }
func parse_post_blocks(content: String) -> [Block] { func parse_post_blocks(content: String) -> [Block] {

View File

@@ -18,7 +18,7 @@ struct PushNotificationClient {
mutating func set_device_token(new_device_token: Data) async throws { mutating func set_device_token(new_device_token: Data) async throws {
self.device_token = new_device_token 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() try await self.send_token()
} }
} }

View File

@@ -155,8 +155,8 @@ class UserSettingsStore: ObservableObject {
@Setting(key: "like_notification", default_value: true) @Setting(key: "like_notification", default_value: true)
var like_notification: Bool var like_notification: Bool
@StringSetting(key: "notifications_mode", default_value: .local) @StringSetting(key: "notification_mode", default_value: .push)
var notifications_mode: NotificationsMode var notification_mode: NotificationsMode
@Setting(key: "notification_only_from_following", default_value: false) @Setting(key: "notification_only_from_following", default_value: false)
var notification_only_from_following: Bool var notification_only_from_following: Bool
@@ -207,8 +207,9 @@ class UserSettingsStore: ObservableObject {
@Setting(key: "always_show_onboarding_suggestions", default_value: false) @Setting(key: "always_show_onboarding_suggestions", default_value: false)
var always_show_onboarding_suggestions: Bool var always_show_onboarding_suggestions: Bool
@Setting(key: "enable_experimental_push_notifications", default_value: false) // @Setting(key: "enable_experimental_push_notifications", default_value: false)
var enable_experimental_push_notifications: Bool // 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) @StringSetting(key: "push_notification_environment", default_value: .production)
var push_notification_environment: PushNotificationClient.Environment var push_notification_environment: PushNotificationClient.Environment

View File

@@ -14,12 +14,12 @@ struct FriendsButton: View {
Button(action: { Button(action: {
switch self.filter { switch self.filter {
case .all: case .all:
self.filter = .friends self.filter = .friends_of_friends
case .friends: case .friends_of_friends:
self.filter = .all self.filter = .all
} }
}) { }) {
if filter == .friends { if filter == .friends_of_friends {
LINEAR_GRADIENT LINEAR_GRADIENT
.mask(Image("user-added") .mask(Image("user-added")
.resizable() .resizable()
@@ -28,7 +28,7 @@ struct FriendsButton: View {
Image("user-added") Image("user-added")
.resizable() .resizable()
.frame(width: 28, height: 28) .frame(width: 28, height: 28)
.foregroundColor(DamusColors.adaptableGrey) .foregroundColor(.gray)
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)

View File

@@ -103,7 +103,7 @@ struct DirectMessagesView: View {
func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageModel]) -> Bool { func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageModel]) -> Bool {
for dm in dms { 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 return true
} }
} }

View File

@@ -120,8 +120,7 @@ struct NoteContentView: View {
EventView(damus: damus_state, event: self.event, options: .embedded_text_only) EventView(damus: damus_state, event: self.event, options: .embedded_text_only)
.padding(.top) .padding(.top)
} }
.background(.thinMaterial) .background(.thickMaterial)
.preferredColorScheme(.dark)
.onTapGesture(perform: { .onTapGesture(perform: {
damus_state.nav.push(route: Route.Thread(thread: .init(event: self.event, damus_state: damus_state))) damus_state.nav.push(route: Route.Thread(thread: .init(event: self.event, damus_state: damus_state)))
dismiss() dismiss()

View File

@@ -56,6 +56,7 @@ struct NotificationsView: View {
@ObservedObject var notifications: NotificationsModel @ObservedObject var notifications: NotificationsModel
@StateObject var filter = NotificationFilter() @StateObject var filter = NotificationFilter()
@SceneStorage("NotificationsView.filter_state") var filter_state: NotificationFilterState = .all @SceneStorage("NotificationsView.filter_state") var filter_state: NotificationFilterState = .all
@Binding var subtitle: String?
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@@ -99,6 +100,15 @@ struct NotificationsView: View {
.tag(NotificationFilterState.replies) .tag(NotificationFilterState.replies)
} }
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(
action: { state.nav.push(route: Route.NotificationSettings(settings: state.settings)) },
label: {
Image("settings")
.foregroundColor(.gray)
}
)
}
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
if would_filter_non_friends_from_notifications(contacts: state.contacts, state: filter_state, items: self.notifications.notifications) { if would_filter_non_friends_from_notifications(contacts: state.contacts, state: filter_state, items: self.notifications.notifications) {
FriendsButton(filter: $filter.fine_filter) FriendsButton(filter: $filter.fine_filter)
@@ -107,12 +117,14 @@ struct NotificationsView: View {
} }
.onChange(of: filter.fine_filter) { val in .onChange(of: filter.fine_filter) { val in
state.settings.friend_filter = val state.settings.friend_filter = val
self.subtitle = filter.fine_filter.description()
} }
.onChange(of: filter_state) { val in .onChange(of: filter_state) { val in
filter.state = val filter.state = val
} }
.onAppear { .onAppear {
self.filter.fine_filter = state.settings.friend_filter self.filter.fine_filter = state.settings.friend_filter
self.subtitle = filter.fine_filter.description()
filter.state = filter_state filter.state = filter_state
} }
.safeAreaInset(edge: .top, spacing: 0) { .safeAreaInset(edge: .top, spacing: 0) {
@@ -163,7 +175,7 @@ struct NotificationsView: View {
struct NotificationsView_Previews: PreviewProvider { struct NotificationsView_Previews: PreviewProvider {
static var previews: some View { 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 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 return true
} }
} }

View File

@@ -64,7 +64,6 @@ struct DamusPurpleAccountView: View {
.padding(.bottom, 20) .padding(.bottom, 20)
} }
.foregroundColor(.white.opacity(0.8)) .foregroundColor(.white.opacity(0.8))
.preferredColorScheme(.dark)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
.padding() .padding()
} }
@@ -81,7 +80,8 @@ struct DamusPurpleAccountView: View {
SupporterBadge( SupporterBadge(
percent: nil, percent: nil,
purple_account: account, purple_account: account,
style: .full style: .full,
text_color: .white
) )
} }
} }

View File

@@ -18,10 +18,6 @@ struct DeveloperSettingsView: View {
.toggleStyle(.switch) .toggleStyle(.switch)
if settings.developer_mode { 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("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)."), 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( selection: Binding(
get: { () -> PushNotificationClient.Environment in get: { () -> PushNotificationClient.Environment in

View File

@@ -36,7 +36,7 @@ struct NotificationSettingsView: View {
do { do {
try await damus_state.push_notification_client.send_token() try await damus_state.push_notification_client.send_token()
await self.sync_up_remote_notification_settings() await self.sync_up_remote_notification_settings()
settings.notifications_mode = new_value settings.notification_mode = new_value
} }
catch { 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) 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 { Task {
do { do {
try await damus_state.push_notification_client.revoke_token() 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 notification_preferences_sync_state = .not_applicable
} }
catch { catch {
@@ -67,7 +67,7 @@ struct NotificationSettingsView: View {
set: { new_value in set: { new_value in
let old_value = raw_binding.wrappedValue let old_value = raw_binding.wrappedValue
raw_binding.wrappedValue = new_value raw_binding.wrappedValue = new_value
if self.settings.notifications_mode == .push { if self.settings.notification_mode == .push {
Task { Task {
await self.send_push_notification_preferences(on_failure: { await self.send_push_notification_preferences(on_failure: {
raw_binding.wrappedValue = old_value raw_binding.wrappedValue = old_value
@@ -114,7 +114,7 @@ struct NotificationSettingsView: View {
var body: some View { var body: some View {
Form { Form {
if settings.enable_experimental_push_notifications { if settings.enable_push_notifications {
Section( Section(
header: Text("General", comment: "Section header for general damus notifications user configuration"), header: Text("General", comment: "Section header for general damus notifications user configuration"),
footer: VStack { 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)."), 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( selection: Binding(
get: { settings.notifications_mode }, get: { settings.notification_mode },
set: { newValue in set: { newValue in
self.try_to_set_notifications_mode(new_value: newValue) self.try_to_set_notifications_mode(new_value: newValue)
} }
@@ -194,7 +194,7 @@ struct NotificationSettingsView: View {
} }
.onAppear(perform: { .onAppear(perform: {
Task { Task {
if self.settings.notifications_mode == .push { if self.settings.notification_mode == .push {
await self.sync_up_remote_notification_settings() await self.sync_up_remote_notification_settings()
} }
} }

View File

@@ -57,7 +57,8 @@ class ReplyTests: XCTestCase {
let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])! let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks
let post_blocks = parse_post_blocks(content: content) 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) let tr = interpret_event_refs(tags: ev.tags)
XCTAssertNil(tr) XCTAssertNil(tr)