From d71451489bb4d008f4ec2529468b15cd3f94d384 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 20:03:29 +0100 Subject: [PATCH 01/22] make FlagOption equatable --- Nos/Models/FlagOption.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nos/Models/FlagOption.swift b/Nos/Models/FlagOption.swift index 35f823b89..79eded9e4 100644 --- a/Nos/Models/FlagOption.swift +++ b/Nos/Models/FlagOption.swift @@ -6,7 +6,7 @@ import Foundation /// - `info`: An optional message that will be displayed when the user has selected a particular flag. /// - `id`: A unique identifier for the flagging option, based on the `title`. -struct FlagOption: Identifiable { +struct FlagOption: Identifiable, Equatable { let title: String let description: String? let info: String? From 41e98f3038b7d6ee2f5571deeb98cc130731e73a Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 20:03:51 +0100 Subject: [PATCH 02/22] add slide in from left animation --- Nos/Views/Moderation/ContentFlagView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Nos/Views/Moderation/ContentFlagView.swift b/Nos/Views/Moderation/ContentFlagView.swift index 1a42ac0a6..1f34f8e66 100644 --- a/Nos/Views/Moderation/ContentFlagView.swift +++ b/Nos/Views/Moderation/ContentFlagView.swift @@ -23,11 +23,13 @@ struct ContentFlagView: View { title: String(localized: .localizable.flagContentSendTitle), subtitle: nil ) + .transition(.move(edge: .leading).combined(with: .opacity)) } } } .padding() .background(Color.appBg) + .animation(.easeInOut, value: selectedFlagOptionCategory) } } From 546caeba27414ec931684dbdf794fc5e1f1cdda6 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 20:42:16 +0100 Subject: [PATCH 03/22] add checkmark asset --- .../Circular Checkmark.pdf | Bin 0 -> 39476 bytes .../circular-checkmark.imageset/Contents.json | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 Nos/Assets/Assets.xcassets/circular-checkmark.imageset/Circular Checkmark.pdf create mode 100644 Nos/Assets/Assets.xcassets/circular-checkmark.imageset/Contents.json diff --git a/Nos/Assets/Assets.xcassets/circular-checkmark.imageset/Circular Checkmark.pdf b/Nos/Assets/Assets.xcassets/circular-checkmark.imageset/Circular Checkmark.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fd86b1aef8598b0a97e2b84e07575d7b85760249 GIT binary patch literal 39476 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 Z1!T$5$<)DVjeik<7Dmwt3d*U;{~v3GrMUnA literal 0 HcmV?d00001 diff --git a/Nos/Assets/Assets.xcassets/circular-checkmark.imageset/Contents.json b/Nos/Assets/Assets.xcassets/circular-checkmark.imageset/Contents.json new file mode 100644 index 000000000..b17197041 --- /dev/null +++ b/Nos/Assets/Assets.xcassets/circular-checkmark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Circular Checkmark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} From 592d3155b78f184d4f865b1929d15d3ec1e1a5cf Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 20:56:19 +0100 Subject: [PATCH 04/22] add localized strings --- Nos/Assets/Localization/Localizable.xcstrings | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings index 3d6c7bb48..d0ebc6c73 100644 --- a/Nos/Assets/Localization/Localizable.xcstrings +++ b/Nos/Assets/Localization/Localizable.xcstrings @@ -4361,24 +4361,24 @@ } } }, - "flagContentFlagPubiclyDescription" : { + "flagContentCategoryTitle" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "This flag will be attributed to your account" + "value" : "Create a content flag for this post that other users in the network can see." } } } }, - "flagContentCategoryTitle" : { + "flagContentFlagPubiclyDescription" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Create a content flag for this post that other users in the network can see." + "value" : "This flag will be attributed to your account" } } } @@ -5596,6 +5596,17 @@ } } }, + "keepOnHelpingUs" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keep on helping us make Nostr a better place for everyone!" + } + } + } + }, "keys" : { "extractionState" : "manual", "localizations" : { @@ -12256,6 +12267,17 @@ } } }, + "thanksForTag" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thanks for your tag." + } + } + } + }, "thatNameIsTaken" : { "extractionState" : "manual", "localizations" : { From a2019c36e001fd74fb251448bb2084e6323fc02d Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 21:05:00 +0100 Subject: [PATCH 05/22] add flagContent string --- Nos/Assets/Localization/Localizable.xcstrings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings index d0ebc6c73..c4f4aa9a1 100644 --- a/Nos/Assets/Localization/Localizable.xcstrings +++ b/Nos/Assets/Localization/Localizable.xcstrings @@ -4350,6 +4350,17 @@ } } }, + "flagContent" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Flag Content" + } + } + } + }, "flagContentCategoryDescription" : { "extractionState" : "manual", "localizations" : { From 6df3809537c59cbb81279a662b03a05fe17bab6e Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 21:05:12 +0100 Subject: [PATCH 06/22] update FlagOption --- Nos/Models/FlagOption.swift | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/Nos/Models/FlagOption.swift b/Nos/Models/FlagOption.swift index 79eded9e4..73cedb8c7 100644 --- a/Nos/Models/FlagOption.swift +++ b/Nos/Models/FlagOption.swift @@ -5,39 +5,46 @@ import Foundation /// - `description`: An optional description that provides more detail about the flagging option. /// - `info`: An optional message that will be displayed when the user has selected a particular flag. /// - `id`: A unique identifier for the flagging option, based on the `title`. +/// - `category`: The specific category thet the selected flagging option falls in. struct FlagOption: Identifiable, Equatable { let title: String let description: String? let info: String? var id: String { title } + var category: Any /// `FlagOption` instances representing different categories of content that can be flagged. static let flagContentCategories: [FlagOption] = [ FlagOption( title: String(localized: .localizable.flagContentSpamTitle), description: nil, - info: nil + info: nil, + category: ReportCategoryType.spam ), FlagOption( title: String(localized: .localizable.flagContentHarassmentTitle), description: String(localized: .localizable.flagContentHarassmentDescription), - info: nil + info: nil, + category: ReportCategoryType.harassment ), FlagOption( title: "NSFW", description: String(localized: .localizable.flagContentNudityDescription), - info: nil + info: nil, + category: ReportCategoryType.nsfw ), FlagOption( title: String(localized: .localizable.flagContentIllegalTitle), description: String(localized: .localizable.flagContentIllegalDescription), - info: nil + info: nil, + category: ReportCategoryType.illegal ), FlagOption( title: String(localized: .localizable.flagContentOtherTitle), description: String(localized: .localizable.flagContentOtherDescription), - info: nil + info: nil, + category: ReportCategoryType.other ) ] @@ -46,12 +53,24 @@ struct FlagOption: Identifiable, Equatable { FlagOption( title: String(localized: .localizable.flagContentSendToNosTitle), description: String(localized: .localizable.flagContentSendToNosDescription), - info: String(localized: .localizable.flagContentSendToNosInfo) + info: String(localized: .localizable.flagContentSendToNosInfo), + category: SendFlagPrivacy.sendToNos ), FlagOption( title: String(localized: .localizable.flagContentFlagPubiclyTitle), description: String(localized: .localizable.flagContentFlagPubiclyDescription), - info: nil + info: nil, + category: SendFlagPrivacy.publicly ) ] + + static func == (lhs: FlagOption, rhs: FlagOption) -> Bool { + lhs.id == rhs.id + } +} + +/// Specifies whether a flag should be sent privately to Nos or made public. +enum SendFlagPrivacy { + case sendToNos + case publicly } From d3ae2bee683d288cc521e237ba7ad6ec4b5fb89f Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 21:05:33 +0100 Subject: [PATCH 07/22] update ContentFlagView --- Nos/Views/Moderation/ContentFlagView.swift | 88 +++++++++++++++++++--- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/Nos/Views/Moderation/ContentFlagView.swift b/Nos/Views/Moderation/ContentFlagView.swift index 1f34f8e66..1d0e10a08 100644 --- a/Nos/Views/Moderation/ContentFlagView.swift +++ b/Nos/Views/Moderation/ContentFlagView.swift @@ -5,15 +5,60 @@ import SwiftUI struct ContentFlagView: View { @Binding var selectedFlagOptionCategory: FlagOption? @Binding var selectedSendOptionCategory: FlagOption? + @Binding var showSuccessView: Bool + var sendAction: () -> Void + @Environment(\.dismiss) private var dismiss var body: some View { - ScrollView { + ZStack { + Color.appBg.ignoresSafeArea() + Group { + if showSuccessView { + successView + } else { + categoryView + } + } + .animation(.easeInOut, value: selectedFlagOptionCategory) + .padding() + .nosNavigationBar(title: .localizable.flagContent) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: { + dismiss() + }, label: { + Text(.localizable.cancel) + .foregroundColor(.primaryTxt) + }) + .opacity(showSuccessView ? 0 : 1) + .disabled(showSuccessView) + } + ToolbarItem(placement: .navigationBarTrailing) { + ActionButton( + title: showSuccessView ? .localizable.done : .localizable.send, + action: { + if showSuccessView { + dismiss() + } else { + sendAction() + } + } + ) + .opacity(selectedSendOptionCategory == nil ? 0.5 : 1) + .disabled(selectedSendOptionCategory == nil) + } + } + } + } + + private var categoryView: some View { + ScrollView(showsIndicators: false) { VStack(spacing: 30) { FlagOptionPicker( selectedOption: $selectedFlagOptionCategory, options: FlagOption.flagContentCategories, - title: String(localized: .localizable.flagContentCategoryTitle), - subtitle: String(localized: .localizable.flagContentCategoryDescription) + title: String(localized: .localizable.reportContent), + subtitle: String(localized: .localizable.reportContentMessage) ) if selectedFlagOptionCategory != nil { @@ -27,9 +72,27 @@ struct ContentFlagView: View { } } } - .padding() - .background(Color.appBg) - .animation(.easeInOut, value: selectedFlagOptionCategory) + } + + private var successView: some View { + VStack(spacing: 30) { + Image.circularCheckmark + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 116) + + Text(String(localized: .localizable.thanksForTag)) + .foregroundColor(.primaryTxt) + .font(.clarity(.regular, textStyle: .title2)) + .padding(.horizontal, 62) + + Text(String(localized: .localizable.keepOnHelpingUs)) + .padding(.horizontal, 68) + .foregroundColor(.secondaryTxt) + .multilineTextAlignment(.center) + .lineSpacing(6) + .font(.clarity(.regular, textStyle: .subheadline)) + } } } @@ -37,12 +100,17 @@ struct ContentFlagView: View { struct PreviewWrapper: View { @State private var selectedFlagOptionCategory: FlagOption? @State private var selectedSendOptionCategory: FlagOption? + @State private var showSuccessView = true var body: some View { - ContentFlagView( - selectedFlagOptionCategory: $selectedFlagOptionCategory, - selectedSendOptionCategory: $selectedSendOptionCategory - ) + NavigationStack { + ContentFlagView( + selectedFlagOptionCategory: $selectedFlagOptionCategory, + selectedSendOptionCategory: $selectedSendOptionCategory, + showSuccessView: $showSuccessView, + sendAction: {} + ) + } .onAppear { selectedFlagOptionCategory = nil } From dfe28bd370c8bda757bdf7613937bd7a28a1b440 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 21:07:10 +0100 Subject: [PATCH 08/22] update ReportMenuModifier --- Nos/Views/Modifiers/ReportMenuModifier.swift | 31 ++++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Nos/Views/Modifiers/ReportMenuModifier.swift b/Nos/Views/Modifiers/ReportMenuModifier.swift index 9a6238f38..a73f427e4 100644 --- a/Nos/Views/Modifiers/ReportMenuModifier.swift +++ b/Nos/Views/Modifiers/ReportMenuModifier.swift @@ -17,6 +17,7 @@ struct ReportMenuModifier: ViewModifier { @State private var confirmationDialogState: ConfirmationDialogState? @State private var selectedFlagOption: FlagOption? @State private var selectedFlagSendOption: FlagOption? + @State private var showFlagSuccessView = false @Environment(\.managedObjectContext) private var viewContext @Dependency(\.featureFlags) private var featureFlags @@ -38,10 +39,19 @@ struct ReportMenuModifier: ViewModifier { case .note: content .sheet(isPresented: $isPresented) { - ContentFlagView( - selectedFlagOptionCategory: $selectedFlagOption, - selectedSendOptionCategory: $selectedFlagSendOption - ) + NavigationStack { + ContentFlagView( + selectedFlagOptionCategory: $selectedFlagOption, + selectedSendOptionCategory: $selectedFlagSendOption, + showSuccessView: $showFlagSuccessView, + sendAction: { + if let selectCategory = selectedFlagOption?.category as? ReportCategory { + publishReportForNewModerationFlow(selectCategory) + showFlagSuccessView = true + } + } + ) + } } case .author: oldModerationFlow(content: content) @@ -221,8 +231,17 @@ struct ReportMenuModifier: ViewModifier { } } } - - /// Publishes a report based on user input + + /// Publishes a report based on the categories the user selected for the new moderation flow. + func publishReportForNewModerationFlow(_ selectedCategory: ReportCategory) { + if selectedFlagSendOption?.category as? SendFlagPrivacy == .sendToNos { + sendToNos(selectedCategory) + } else { + flagPublicly(selectedCategory) + } + } + + /// Publishes a report based on user input for the old moderation flow. func publishReport(_ userSelection: UserSelection?) { switch userSelection { case .sendToNos(let selectedCategory): From 8cd11f558f5f48dc8b69f94090bf125c92737e62 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 21:15:44 +0100 Subject: [PATCH 09/22] reset selections on cancel --- Nos/Views/Moderation/ContentFlagView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Nos/Views/Moderation/ContentFlagView.swift b/Nos/Views/Moderation/ContentFlagView.swift index 1d0e10a08..e24408dc0 100644 --- a/Nos/Views/Moderation/ContentFlagView.swift +++ b/Nos/Views/Moderation/ContentFlagView.swift @@ -26,6 +26,7 @@ struct ContentFlagView: View { ToolbarItem(placement: .navigationBarLeading) { Button(action: { dismiss() + resetSelections() }, label: { Text(.localizable.cancel) .foregroundColor(.primaryTxt) @@ -51,6 +52,11 @@ struct ContentFlagView: View { } } + private func resetSelections() { + selectedFlagOptionCategory = nil + selectedSendOptionCategory = nil + } + private var categoryView: some View { ScrollView(showsIndicators: false) { VStack(spacing: 30) { From a59d3fc02031e5dc5cb1761825d9bd2de9ade10a Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 20 Sep 2024 21:20:13 +0100 Subject: [PATCH 10/22] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e07e517fa..9e0be77e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored feature flag and added a feature flag toggle for “Enable new moderation flow” to Staging builds. [#1496](https://github.com/planetary-social/nos/issues/1496) - Refactored list row gradient background. - Added SwiftSoup to parse Open Graph metadata. [#1165](https://github.com/planetary-social/nos/issues/1165) +- Added a new flow to flag notes. Currently behind the “Enable new moderation flow” feature flag. [#1489](https://github.com/planetary-social/nos/issues/1489) ## [0.1.26] - 2024-09-09Z From 919542e65c05a5b03d6bf94893df6c180bcf1307 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Mon, 23 Sep 2024 15:18:07 +0100 Subject: [PATCH 11/22] refactor code --- Nos/Models/FlagOption.swift | 22 +++++++++----- Nos/Views/Modifiers/ReportMenuModifier.swift | 31 ++++++++++++++++---- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Nos/Models/FlagOption.swift b/Nos/Models/FlagOption.swift index 73cedb8c7..407dda99f 100644 --- a/Nos/Models/FlagOption.swift +++ b/Nos/Models/FlagOption.swift @@ -12,7 +12,7 @@ struct FlagOption: Identifiable, Equatable { let description: String? let info: String? var id: String { title } - var category: Any + var category: FlagCategory /// `FlagOption` instances representing different categories of content that can be flagged. static let flagContentCategories: [FlagOption] = [ @@ -20,31 +20,31 @@ struct FlagOption: Identifiable, Equatable { title: String(localized: .localizable.flagContentSpamTitle), description: nil, info: nil, - category: ReportCategoryType.spam + category: .report(ReportCategoryType.spam) ), FlagOption( title: String(localized: .localizable.flagContentHarassmentTitle), description: String(localized: .localizable.flagContentHarassmentDescription), info: nil, - category: ReportCategoryType.harassment + category: .report(ReportCategoryType.harassment) ), FlagOption( title: "NSFW", description: String(localized: .localizable.flagContentNudityDescription), info: nil, - category: ReportCategoryType.nsfw + category: .report(ReportCategoryType.nsfw) ), FlagOption( title: String(localized: .localizable.flagContentIllegalTitle), description: String(localized: .localizable.flagContentIllegalDescription), info: nil, - category: ReportCategoryType.illegal + category: .report(ReportCategoryType.illegal) ), FlagOption( title: String(localized: .localizable.flagContentOtherTitle), description: String(localized: .localizable.flagContentOtherDescription), info: nil, - category: ReportCategoryType.other + category: .report(ReportCategoryType.other) ) ] @@ -54,13 +54,13 @@ struct FlagOption: Identifiable, Equatable { title: String(localized: .localizable.flagContentSendToNosTitle), description: String(localized: .localizable.flagContentSendToNosDescription), info: String(localized: .localizable.flagContentSendToNosInfo), - category: SendFlagPrivacy.sendToNos + category: .privacy(.sendToNos) ), FlagOption( title: String(localized: .localizable.flagContentFlagPubiclyTitle), description: String(localized: .localizable.flagContentFlagPubiclyDescription), info: nil, - category: SendFlagPrivacy.publicly + category: .privacy(.publicly) ) ] @@ -69,6 +69,12 @@ struct FlagOption: Identifiable, Equatable { } } +/// Specifies the category associated with a specific flag. +enum FlagCategory { + case report(ReportCategory) + case privacy(SendFlagPrivacy) +} + /// Specifies whether a flag should be sent privately to Nos or made public. enum SendFlagPrivacy { case sendToNos diff --git a/Nos/Views/Modifiers/ReportMenuModifier.swift b/Nos/Views/Modifiers/ReportMenuModifier.swift index a73f427e4..0137495c9 100644 --- a/Nos/Views/Modifiers/ReportMenuModifier.swift +++ b/Nos/Views/Modifiers/ReportMenuModifier.swift @@ -45,7 +45,7 @@ struct ReportMenuModifier: ViewModifier { selectedSendOptionCategory: $selectedFlagSendOption, showSuccessView: $showFlagSuccessView, sendAction: { - if let selectCategory = selectedFlagOption?.category as? ReportCategory { + if let selectCategory = selectedFlagOption?.category { publishReportForNewModerationFlow(selectCategory) showFlagSuccessView = true } @@ -233,11 +233,32 @@ struct ReportMenuModifier: ViewModifier { } /// Publishes a report based on the categories the user selected for the new moderation flow. - func publishReportForNewModerationFlow(_ selectedCategory: ReportCategory) { - if selectedFlagSendOption?.category as? SendFlagPrivacy == .sendToNos { - sendToNos(selectedCategory) + private func publishReportForNewModerationFlow(_ selectedCategory: FlagCategory) { + if case .privacy(let privacyCategory) = selectedFlagSendOption?.category, privacyCategory == .sendToNos { + sendToNosForNewModerationFlow(selectedCategory) } else { - flagPublicly(selectedCategory) + flagPubliclyForNewModerationFlow(selectedCategory) + } + } + + private func sendToNosForNewModerationFlow(_ selectedCategory: FlagCategory) { + if case .report(let reportCategory) = selectedCategory { + // Call the publisher with the extracted ReportCategory + ReportPublisher().publishPrivateReport( + for: reportedObject, + category: reportCategory, + context: viewContext + ) + } + } + + private func flagPubliclyForNewModerationFlow(_ selectedCategory: FlagCategory) { + if case .report(let reportCategory) = selectedCategory { + ReportPublisher().publishPublicReport( + for: reportedObject, + category: reportCategory, + context: viewContext + ) } } From 2436dde9805a9cdc795bf68e5237454e8dd249c8 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Tue, 24 Sep 2024 15:15:39 +0100 Subject: [PATCH 12/22] fix build error --- Nos/Models/FlagOption.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nos/Models/FlagOption.swift b/Nos/Models/FlagOption.swift index 448874ef3..5427a5902 100644 --- a/Nos/Models/FlagOption.swift +++ b/Nos/Models/FlagOption.swift @@ -5,7 +5,7 @@ import Foundation /// - `description`: An optional description that provides more detail about the flagging option. /// - `info`: An optional message that will be displayed when the user has selected a particular flag. /// - `id`: A unique identifier for the flagging option, based on the `title`. -struct FlagOption: Identifiable { +struct FlagOption: Identifiable, Equatable { let title: String let description: String? let info: String? From eb714978d403284e79ed0699e70cb546d835909c Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Tue, 24 Sep 2024 16:56:50 +0100 Subject: [PATCH 13/22] fix localization copy --- Nos/Assets/Localization/Localizable.xcstrings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings index c32aff20b..d36ec22c6 100644 --- a/Nos/Assets/Localization/Localizable.xcstrings +++ b/Nos/Assets/Localization/Localizable.xcstrings @@ -4543,7 +4543,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Send to Nos or Flag Publicly" + "value" : "Send to Nos or Flag Publicly?" } } } From 7d1a79e4723204bb0506f1de6bc7b4a7338166e3 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Tue, 24 Sep 2024 16:57:07 +0100 Subject: [PATCH 14/22] fix documentation --- Nos/Models/FlagOption.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Nos/Models/FlagOption.swift b/Nos/Models/FlagOption.swift index b023a7b44..3b5e2eaff 100644 --- a/Nos/Models/FlagOption.swift +++ b/Nos/Models/FlagOption.swift @@ -1,16 +1,20 @@ import Foundation /// A model representing a flagging option used in content moderation. -/// - `title`: The title of the flagging option. -/// - `description`: An optional description that provides more detail about the flagging option. -/// - `info`: An optional message that will be displayed when the user has selected a particular flag. -/// - `id`: A unique identifier for the flagging option, based on the `title`. -/// - `category`: The specific category thet the selected flagging option falls in. struct FlagOption: Identifiable, Equatable { + /// The title of the flagging option. let title: String + + /// An optional description that provides more detail about the flagging option. let description: String? + + /// An optional message that will be displayed when the user has selected a particular flag. let info: String? + + /// A unique identifier for the flagging option, based on the `title`. var id: String { title } + + /// The specific category thet the selected flagging option falls in. var category: FlagCategory /// `FlagOption` instances representing different categories of content that can be flagged. From 024aebc54d39e639d2bfc77878806421ab337ed4 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Tue, 24 Sep 2024 16:57:26 +0100 Subject: [PATCH 15/22] reset states after done button is pressed --- Nos/Views/Moderation/ContentFlagView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Nos/Views/Moderation/ContentFlagView.swift b/Nos/Views/Moderation/ContentFlagView.swift index e24408dc0..81e7e1b7b 100644 --- a/Nos/Views/Moderation/ContentFlagView.swift +++ b/Nos/Views/Moderation/ContentFlagView.swift @@ -40,6 +40,8 @@ struct ContentFlagView: View { action: { if showSuccessView { dismiss() + resetSelections() + showSuccessView = false } else { sendAction() } @@ -63,8 +65,8 @@ struct ContentFlagView: View { FlagOptionPicker( selectedOption: $selectedFlagOptionCategory, options: FlagOption.flagContentCategories, - title: String(localized: .localizable.reportContent), - subtitle: String(localized: .localizable.reportContentMessage) + title: String(localized: .localizable.flagContentCategoryTitle), + subtitle: String(localized: .localizable.flagContentCategoryDescription) ) if selectedFlagOptionCategory != nil { From 73ac388baf9274267efd6cf1da5f88400178853a Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Tue, 24 Sep 2024 13:10:42 -0700 Subject: [PATCH 16/22] publish empty metadata event and empty contact list on delete account #1530 --- Nos/Models/JSONEvent+Kinds.swift | 18 +++++ Nos/Service/CurrentUser+PublishEvents.swift | 82 ++++++++++++++------- Nos/Service/CurrentUser.swift | 6 ++ Nos/Service/Relay/RelayService.swift | 17 +++-- NosTests/Models/JSONEventTests.swift | 46 ++++++++++++ 5 files changed, 137 insertions(+), 32 deletions(-) diff --git a/Nos/Models/JSONEvent+Kinds.swift b/Nos/Models/JSONEvent+Kinds.swift index e11ebc3dc..614294258 100644 --- a/Nos/Models/JSONEvent+Kinds.swift +++ b/Nos/Models/JSONEvent+Kinds.swift @@ -2,6 +2,24 @@ import Foundation extension JSONEvent { + /// An event that represents the user's contact list (who they are following) and their relays. + /// - Parameters: + /// - pubKey: The pubkey of the user whose contact list and relays the event represents. + /// - tags: The "p" tags of followed profiles. + /// - relays: Relays the user wishes to associate with their profile. + /// - Returns: The ``JSONEvent`` of the contact list. + static func contactList(pubKey: String, tags: [[String]], relayAddresses: [String]) -> JSONEvent { + let relayStrings = relayAddresses.map { "\"\($0)\":{\"write\":true,\"read\":true}" } + let content = "{" + relayStrings.joined(separator: ",") + "}" + + return JSONEvent( + pubKey: pubKey, + kind: .contactList, + tags: tags, + content: content + ) + } + /// An event that represents the user's request for all of their published notes to be removed from relays. /// - Parameters: /// - pubKey: The public key of the user making the request. diff --git a/Nos/Service/CurrentUser+PublishEvents.swift b/Nos/Service/CurrentUser+PublishEvents.swift index 9bde83db6..f14a961c0 100644 --- a/Nos/Service/CurrentUser+PublishEvents.swift +++ b/Nos/Service/CurrentUser+PublishEvents.swift @@ -118,33 +118,24 @@ extension CurrentUser { } @MainActor func publishContactList(tags: [[String]]) async { - guard let pubKey = publicKeyHex else { - Log.debug("Error: no pubKey") + guard let keyPair else { + Log.debug("Error: no key pair") return } - guard let relays = author?.relays else { + let pubKey = keyPair.publicKey.hex + + guard let relays = author?.relays.compactMap({ $0.address }) else { Log.debug("Error: No relay service") return } - - var relayString = "{" - for relay in relays { - if let address = relay.address { - relayString += "\"\(address)\":{\"write\":true,\"read\":true}," - } - } - relayString.removeLast() - relayString += "}" - let jsonEvent = JSONEvent(pubKey: pubKey, kind: .contactList, tags: tags, content: relayString) + let jsonEvent = JSONEvent.contactList(pubKey: pubKey, tags: tags, relayAddresses: relays) - if let pair = keyPair { - do { - try await relayService.publishToAll(event: jsonEvent, signingKey: pair, context: viewContext) - } catch { - Log.debug("failed to update Follows \(error.localizedDescription)") - } + do { + try await relayService.publishToAll(event: jsonEvent, signingKey: keyPair, context: viewContext) + } catch { + Log.debug("failed to update Follows \(error.localizedDescription)") } } @@ -203,20 +194,61 @@ extension CurrentUser { await publishContactList(tags: stillFollowingKeys.pTags) } - @MainActor func publishRequestToVanish(to relays: [URL]? = nil, reason: String? = nil) async throws { + @MainActor func publishAccountDeletedMetadata() async throws { + guard let author else { + Log.error("Error: no author") + return + } + + author.about = nil + author.displayName = "Account deleted" + author.name = "Account deleted" + author.website = nil + author.nip05 = nil + author.profilePhotoURL = nil + author.uns = nil + author.rawMetadata = nil + + try viewContext.save() + try await publishMetadata() + } + + @MainActor func publishEmptyFollowList() async throws { + guard let author else { + Log.error("Error: no author") + return + } + + author.relays = Set() + + try viewContext.save() + await publishContactList(tags: []) + } + + @MainActor func publishRequestToVanish(reason: String? = nil) async throws { guard let keyPair else { Log.debug("Error: no key pair") return } let pubKey = keyPair.publicKey.hex - let jsonEvent = JSONEvent.requestToVanish(pubKey: pubKey, relays: relays, reason: reason) + let jsonEvent = JSONEvent.requestToVanish(pubKey: pubKey, reason: reason) do { - if let relays, !relays.isEmpty { - try await relayService.publish(event: jsonEvent, to: relays, signingKey: keyPair, context: viewContext) - } else { - try await relayService.publishToAll(event: jsonEvent, signingKey: keyPair, context: viewContext) + try await relayService.publishToAll(event: jsonEvent, signingKey: keyPair, context: viewContext) + + if let authorRelays = author?.relays.compactMap({ $0.addressURL }), + !authorRelays.contains(Relay.nosAddress) { + // Make sure the NIP-62 event is always published to relay.nos.social - even if it isn't in + // the user's relay list. This will ensure that our servers see the request and can delete their + // data across all our web services: the relay, our push notification database, the follow + // database, etc. + try await relayService.publish( + event: jsonEvent, + to: Relay.nosAddress, + signingKey: keyPair, + context: viewContext + ) } } catch { Log.debug("Failed to publish request to vanish \(error.localizedDescription)") diff --git a/Nos/Service/CurrentUser.swift b/Nos/Service/CurrentUser.swift index 85c574fef..ce88d83a7 100644 --- a/Nos/Service/CurrentUser.swift +++ b/Nos/Service/CurrentUser.swift @@ -313,7 +313,13 @@ extension CurrentUser { /// > Warning: This is a destructive action, so be sure that it is actually what the /// user wants. func deleteAccount(appController: AppController) async throws { + try await publishAccountDeletedMetadata() try await publishRequestToVanish() + + // Note: Publishing the empty follow list must be last before logout because + // it will remove the user's relays. + try await publishEmptyFollowList() + await logout(appController: appController) } } diff --git a/Nos/Service/Relay/RelayService.swift b/Nos/Service/Relay/RelayService.swift index 819a12a2b..84500684c 100644 --- a/Nos/Service/Relay/RelayService.swift +++ b/Nos/Service/Relay/RelayService.swift @@ -660,10 +660,16 @@ extension RelayService { signingKey: KeyPair? = nil, context: NSManagedObjectContext ) async throws { - var signedEvent: JSONEvent - - switch signingKey { - case .none: + let signedEvent: JSONEvent + + if let signingKey { + signedEvent = try await signAndSave( + event: event, + signingKey: signingKey, + relayURLs: [relayURL], + in: context + ) + } else { // If you don't provide a key, the event needs to be already signed guard !event.signature.isEmptyOrNil else { Log.error("Missing signature and no key provided for event \(event)") @@ -671,9 +677,6 @@ extension RelayService { } signedEvent = event - - case .some(let keyPair): - signedEvent = try await signAndSave(event: event, signingKey: keyPair, relayURLs: [relayURL], in: context) } await openSocket(to: relayURL, andSend: try signedEvent.buildPublishRequest()) diff --git a/NosTests/Models/JSONEventTests.swift b/NosTests/Models/JSONEventTests.swift index b47d019b1..f64b2d9da 100644 --- a/NosTests/Models/JSONEventTests.swift +++ b/NosTests/Models/JSONEventTests.swift @@ -15,6 +15,52 @@ class JSONEventTests: XCTestCase { XCTAssertEqual(subject.replaceableID, replaceableID) } + func test_contactList_withRelays() { + let pTags = [ + ["p", "91cf94e5ca", "wss://alicerelay.com/", "alice"], + ["p", "14aeb8dad4", "wss://bobrelay.com/nostr", "bob"], + ["p", "612aee610f", "ws://carolrelay.com/ws", "carol"] + ] + let relayAddresses = [ + "wss://relay1.lol", + "wss://relay2.lol" + ] + + let event = JSONEvent.contactList( + pubKey: "", + tags: pTags, + relayAddresses: relayAddresses + ) + + let expectedContent = """ + {"wss://relay1.lol":{"write":true,"read":true},"wss://relay2.lol":{"write":true,"read":true}} + """ + + XCTAssertEqual(event.kind, 3) + XCTAssertEqual(event.tags, pTags) + XCTAssertEqual(event.content, expectedContent) + } + + func test_contactList_withNoRelays() { + let pTags = [ + ["p", "91cf94e5ca", "wss://alicerelay.com/", "alice"], + ["p", "14aeb8dad4", "wss://bobrelay.com/nostr", "bob"], + ["p", "612aee610f", "ws://carolrelay.com/ws", "carol"] + ] + + let event = JSONEvent.contactList( + pubKey: "", + tags: pTags, + relayAddresses: [] + ) + + let expectedContent = "{}" + + XCTAssertEqual(event.kind, 3) + XCTAssertEqual(event.tags, pTags) + XCTAssertEqual(event.content, expectedContent) + } + func test_requestToVanish_fromSpecificRelays() { let event = JSONEvent.requestToVanish( pubKey: "", From a3dc0553dd754a1ea6d609d6d84dc2edba371949 Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Tue, 24 Sep 2024 13:25:34 -0700 Subject: [PATCH 17/22] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd2a3e26..4d7c2bad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Delete Account UI. [#80](https://github.com/planetary-social/nos/issues/80) - Fixed issue where search results weren't sorted properly. [#1485](https://github.com/planetary-social/nos/issues/1485) - Delete all user data when logging out. [#1534](https://github.com/planetary-social/nos/issues/1534) +- Publish empty metadata event and empty contact list on delete account. [#1530](https://github.com/planetary-social/nos/issues/1530) ### Internal Changes - Use NIP-92 media metadata to display media in the proper orientation. Currently behind the “Enable new media display” feature flag. [#1172](https://github.com/planetary-social/nos/issues/1172) From 0d455d43a29a6ff60a4a306a5fa993d485571654 Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Wed, 25 Sep 2024 07:05:42 -0700 Subject: [PATCH 18/22] add clarity to log statements --- Nos/Service/CurrentUser+PublishEvents.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Nos/Service/CurrentUser+PublishEvents.swift b/Nos/Service/CurrentUser+PublishEvents.swift index f14a961c0..95185a24a 100644 --- a/Nos/Service/CurrentUser+PublishEvents.swift +++ b/Nos/Service/CurrentUser+PublishEvents.swift @@ -119,7 +119,7 @@ extension CurrentUser { @MainActor func publishContactList(tags: [[String]]) async { guard let keyPair else { - Log.debug("Error: no key pair") + Log.error("Error: Failed to publish contact list because there was no key pair") return } @@ -196,7 +196,7 @@ extension CurrentUser { @MainActor func publishAccountDeletedMetadata() async throws { guard let author else { - Log.error("Error: no author") + Log.error("Error: Failed to publish account deleted metadata because there was no Author") return } @@ -215,7 +215,7 @@ extension CurrentUser { @MainActor func publishEmptyFollowList() async throws { guard let author else { - Log.error("Error: no author") + Log.error("Error: Failed to publish empty follow list because there was no Author") return } From f359740283fc2cc6ad30028dbfb8839ae7266014 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 25 Sep 2024 11:55:56 -0400 Subject: [PATCH 19/22] Stamping beta deployment --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 698e6b4e0..3b4678ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.27] - 2024-09-25Z + ### Release Notes - Added the option to preview a note before posting it. [#1399](https://github.com/planetary-social/nos/issues/1399) - Fixed side menu accessibility issues. [#1444](https://github.com/planetary-social/nos/issues/1444) From 4fca867ec7539cb5e680e5c857c3493f2187cb1f Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 25 Sep 2024 12:00:13 -0400 Subject: [PATCH 20/22] bump version to 0.2.0 --- Nos.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index 41b0a4f75..a578429a9 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -2840,7 +2840,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 0.1.27; + MARKETING_VERSION = 0.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.verse.Nos-staging"; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME) Staging"; @@ -3013,7 +3013,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 0.1.27; + MARKETING_VERSION = 0.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.verse.Nos-dev"; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME) Dev"; @@ -3283,7 +3283,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 0.1.27; + MARKETING_VERSION = 0.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.verse.Nos; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3338,7 +3338,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 0.1.27; + MARKETING_VERSION = 0.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.verse.Nos; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME)"; From 71d86d0551adb273449cac4b732898286c8c6fff Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 25 Sep 2024 14:15:05 -0400 Subject: [PATCH 21/22] Remove Martin (temporarily) from CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6227fe4b6..b49ed52b0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ # CODEOWNERS # The default owners for everything in the repo. -* @mplorentz @martindsq @joshuatbrown +* @mplorentz @joshuatbrown From dfca712af9de2f63c018b6cca7c29c560c2b1874 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 25 Sep 2024 14:19:06 -0400 Subject: [PATCH 22/22] update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36e74dcd2..47c364811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Release Notes + +### Internal Changes +- Temporarily remove Martin from the list of CODEOWNERS. + ## [0.1.27] - 2024-09-25Z ### Release Notes