diff --git a/CHANGELOG.md b/CHANGELOG.md index fea62f992..53e213f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Migrate ObservableObject to @Observable where possible [#1458](https://github.com/planetary-social/nos/issues/1458) - Added the Create Account onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1594](https://github.com/planetary-social/nos/issues/1594) - Increase build settings timeout in fastlane. [#1662](https://github.com/planetary-social/nos/pull/1662) +- Removed new moderation feature flag. [#1646](https://github.com/planetary-social/nos/issues/1646) ## [0.2.2] - 2024-10-11Z diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings index b65461f57..0f5d62668 100644 --- a/Nos/Assets/Localization/Localizable.xcstrings +++ b/Nos/Assets/Localization/Localizable.xcstrings @@ -5509,35 +5509,6 @@ } } }, - "enableNewModerationFlow" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enable new moderation flow" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Activar nuevo flujo de moderación" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "새로운 모더레이션 흐름 활성화" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "启用新审核流程" - } - } - } - }, "enterCode" : { "extractionState" : "manual", "localizations" : { diff --git a/Nos/Service/FeatureFlags.swift b/Nos/Service/FeatureFlags.swift index 2040f4880..d3038673b 100644 --- a/Nos/Service/FeatureFlags.swift +++ b/Nos/Service/FeatureFlags.swift @@ -4,10 +4,6 @@ import SwiftUI /// Feature flags for enabling experimental or beta features. enum FeatureFlag { - /// Whether the new moderation flow should be enabled or not. - /// - Note: See [#1489](https://github.com/planetary-social/nos/issues/1489) for details on the new moderation flow. - case newModerationFlow - /// Whether the new onboarding flow should be enabled or not. /// - Note: See [Figma](https://www.figma.com/design/6MeujQUXzC1AuviHEHCs0J/Nos---In-Progress?node-id=9221-8504) /// for the new flow. @@ -35,7 +31,6 @@ protocol FeatureFlags { /// Feature flags and their values. private var featureFlags: [FeatureFlag: Bool] = [ - .newModerationFlow: false, .newOnboardingFlow: false ] diff --git a/Nos/Views/Modifiers/ReportMenuModifier.swift b/Nos/Views/Modifiers/ReportMenuModifier.swift index 7b4145bfd..b9645b0a2 100644 --- a/Nos/Views/Modifiers/ReportMenuModifier.swift +++ b/Nos/Views/Modifiers/ReportMenuModifier.swift @@ -8,13 +8,9 @@ import SwiftUINavigation /// category and optionally mute the respective author. struct ReportMenuModifier: ViewModifier { @Binding var isPresented: Bool - + var reportedObject: ReportTarget - - @State private var userSelection: UserSelection? - @State private var confirmReport = false - @State private var showMuteDialog = false - @State private var confirmationDialogState: ConfirmationDialogState? + @State private var selectedFlagOption: FlagOption? @State private var selectedFlagSendOption: FlagOption? @State private var selectedVisibilityOption: FlagOption? @@ -24,18 +20,6 @@ struct ReportMenuModifier: ViewModifier { @Dependency(\.featureFlags) private var featureFlags func body(content: Content) -> some View { - Group { - if featureFlags.isEnabled(.newModerationFlow) { - newModerationFlow(content: content) - } else { - oldModerationFlow(content: content) - } - } - } - - /// Displays the moderation flow based on the reported object type. The old flow is still displayed for the author. - @ViewBuilder - private func newModerationFlow(content: Content) -> some View { switch reportedObject { case .note: content @@ -44,11 +28,11 @@ struct ReportMenuModifier: ViewModifier { ContentFlagView( selectedFlagOptionCategory: $selectedFlagOption, selectedSendOptionCategory: $selectedFlagSendOption, - showSuccessView: $showFlagSuccessView, + showSuccessView: $showFlagSuccessView, flagTarget: reportedObject, sendAction: { if let selectCategory = selectedFlagOption?.category { - publishReportForNewModerationFlow(selectCategory) + publishReport(selectCategory) showFlagSuccessView = true } } @@ -63,11 +47,11 @@ struct ReportMenuModifier: ViewModifier { selectedFlagOption: $selectedFlagOption, selectedSendOption: $selectedFlagSendOption, selectedVisibilityOption: $selectedVisibilityOption, - showSuccessView: $showFlagSuccessView, + showSuccessView: $showFlagSuccessView, flagTarget: reportedObject, sendAction: { let selectCategory = selectedVisibilityOption?.category ?? .visibility(.dontMute) - publishReportForNewModerationFlow(selectCategory) + publishReport(selectCategory) Task { await muteUserIfNeeded() showFlagSuccessView = true @@ -79,130 +63,6 @@ struct ReportMenuModifier: ViewModifier { } } - // swiftlint:disable function_body_length - @ViewBuilder - func oldModerationFlow(content: Content) -> some View { - content - // ReportCategory menu - .confirmationDialog(unwrapping: $confirmationDialogState, action: processUserSelection) - .alert( - String(localized: "confirmFlag"), - isPresented: $confirmReport, - actions: { - Button("confirm") { - publishReport(userSelection) - - if let author = reportedObject.author, !author.muted { - showMuteDialog = true - } - } - Button("cancel", role: .cancel) { - userSelection = nil - } - }, - message: { - let text = getAlertMessage(for: userSelection, with: reportedObject) - Text(text) - } - ) - // Mute user menu - .alert( - "muteUser", - isPresented: $showMuteDialog, - actions: { - if let author = reportedObject.author { - Button("yes") { - Task { - await mute(author: author) - } - } - Button("no") {} - } - }, - message: { - if let author = reportedObject.author { - Text(String.localizedStringWithFormat(String(localized: "mutePrompt"), author.safeName)) - } else { - Text("error") - } - } - ) - .onChange(of: isPresented) { _, shouldPresent in - if shouldPresent { - let message: String - if case .noteCategorySelected = userSelection { - message = String(localized: "reportContentMessage") - } else { - message = String(localized: "flagUserMessage") - } - confirmationDialogState = ConfirmationDialogState( - title: TextState(String(localized: "reportContent")), - message: TextState(message), - buttons: topLevelButtons() - ) - } - } - .onChange(of: confirmationDialogState) { _, newValue in - if newValue == nil { - isPresented = false - } - } - } - // swiftlint:enable function_body_length - - func processUserSelection(_ userSelection: UserSelection?) { - self.userSelection = userSelection - - guard let userSelection else { - return - } - - switch userSelection { - case .noteCategorySelected(let category): - Task { - confirmationDialogState = ConfirmationDialogState( - title: TextState( - String.localizedStringWithFormat(String(localized: "reportActionTitle"), category.displayName) - ), - message: TextState( - String.localizedStringWithFormat(String(localized: "reportActionTitle"), category.displayName) - ), - buttons: [ - ButtonState(action: .send(.sendToNos(category))) { - TextState("Send to Nos") - }, - ButtonState(action: .send(.flagPublicly(category))) { - TextState("Flag Publicly") - } - ] - ) - } - - case .authorCategorySelected(let category): - Task { - confirmationDialogState = ConfirmationDialogState( - title: TextState( - String.localizedStringWithFormat(String(localized: "reportActionTitle"), category.displayName) - ), - message: TextState( - String.localizedStringWithFormat(String(localized: "reportActionTitle"), category.displayName) - ), - buttons: [ - ButtonState(action: .send(.sendToNos(category))) { - TextState("Send to Nos") - }, - ButtonState(action: .send(.flagPublicly(category))) { - TextState("Flag Publicly") - } - ] - ) - } - - case .sendToNos, .flagPublicly: - confirmReport = true - } - } - func mute(author: Author) async { do { try await author.mute(viewContext: viewContext) @@ -223,72 +83,16 @@ struct ReportMenuModifier: ViewModifier { } } - /// An enum to simplify the user selection through the sequence of connected - /// dialogs - enum UserSelection: Equatable { - case noteCategorySelected(ReportCategory) - case authorCategorySelected(ReportCategory) - case sendToNos(ReportCategory) - case flagPublicly(ReportCategory) - - func confirmationAlertMessage(for reportedObject: ReportTarget) -> String { - switch self { - case .sendToNos(let category): - switch reportedObject { - case .note: - return String.localizedStringWithFormat( - String(localized: "reportNoteSendToNosConfirmation"), category.displayName - ) - case .author: - return String(localized: "reportAuthorSendToNosConfirmation") - } - - case .flagPublicly(let category): - return String.localizedStringWithFormat( - String(localized: "reportFlagPubliclyConfirmation"), category.displayName - ) - - case .noteCategorySelected(let category), - .authorCategorySelected(let category): - return String.localizedStringWithFormat( - String(localized: "reportFlagPubliclyConfirmation"), category.displayName - ) - } - } - } - - /// List of the top-level report categories we care about. - func topLevelButtons() -> [ButtonState] { - switch reportedObject { - case .note: - ReportCategory.noteCategories.map { category in - let userSelection = UserSelection.noteCategorySelected(category) - - return ButtonState(action: .send(userSelection)) { - TextState(verbatim: category.displayName) - } - } - case .author: - ReportCategory.authorCategories.map { category in - let userSelection = UserSelection.authorCategorySelected(category) - - return ButtonState(action: .send(userSelection)) { - TextState(verbatim: category.displayName) - } - } - } - } - - /// Publishes a report based on the categories the user selected for the new moderation flow. - private func publishReportForNewModerationFlow(_ selectedCategory: FlagCategory) { + /// Publishes a report based on the category the user selected. + private func publishReport(_ selectedCategory: FlagCategory) { if case .privacy(let privacyCategory) = selectedFlagSendOption?.category, privacyCategory == .sendToNos { - sendToNosForNewModerationFlow(selectedCategory) + sendToNos(selectedCategory) } else { - flagPubliclyForNewModerationFlow(selectedCategory) + flagPublicly(selectedCategory) } } - private func sendToNosForNewModerationFlow(_ selectedCategory: FlagCategory) { + private func sendToNos(_ selectedCategory: FlagCategory) { if case .report(let reportCategory) = selectedCategory { // Call the publisher with the extracted ReportCategory ReportPublisher().publishPrivateReport( @@ -299,7 +103,7 @@ struct ReportMenuModifier: ViewModifier { } } - private func flagPubliclyForNewModerationFlow(_ selectedCategory: FlagCategory) { + private func flagPublicly(_ selectedCategory: FlagCategory) { if case .report(let reportCategory) = selectedCategory { ReportPublisher().publishPublicReport( for: reportedObject, @@ -308,39 +112,6 @@ struct ReportMenuModifier: ViewModifier { ) } } - - /// Publishes a report based on user input for the old moderation flow. - func publishReport(_ userSelection: UserSelection?) { - switch userSelection { - case .sendToNos(let selectedCategory): - sendToNos(selectedCategory) - case .flagPublicly(let selectedCategory): - flagPublicly(selectedCategory) - case .noteCategorySelected, .authorCategorySelected, .none: - // This would be a dev error - Log.error("Invalid user selection") - } - } - - func sendToNos(_ selectedCategory: ReportCategory) { - ReportPublisher().publishPrivateReport( - for: reportedObject, - category: selectedCategory, - context: viewContext - ) - } - - func flagPublicly(_ selectedCategory: ReportCategory) { - ReportPublisher().publishPublicReport( - for: reportedObject, - category: selectedCategory, - context: viewContext - ) - } - - func getAlertMessage(for userSelection: UserSelection?, with reportedObject: ReportTarget) -> String { - userSelection?.confirmationAlertMessage(for: reportedObject) ?? String(localized: "error") - } } extension View { diff --git a/Nos/Views/Settings/SettingsView.swift b/Nos/Views/Settings/SettingsView.swift index b2f63c07b..49f1754b7 100644 --- a/Nos/Views/Settings/SettingsView.swift +++ b/Nos/Views/Settings/SettingsView.swift @@ -289,19 +289,6 @@ struct SettingsView: View { // DEBUG builds will have everything that's in STAGING builds and more. #if STAGING || DEBUG extension SettingsView { - /// Whether the new moderation flow is enabled. - private var isNewModerationFlowEnabled: Binding { - Binding( - get: { featureFlags.isEnabled(.newModerationFlow) }, - set: { featureFlags.setFeature(.newModerationFlow, enabled: $0) } - ) - } - - /// A toggle for the new moderation flow that allows the user to turn the feature on or off. - private var newModerationFlowToggle: some View { - NosToggle("enableNewModerationFlow", isOn: isNewModerationFlowEnabled) - } - /// Whether the new onboarding flow is enabled. private var isNewOnboardingFlowEnabled: Binding { Binding( @@ -322,7 +309,6 @@ extension SettingsView { /// Controls that will appear when the app is built for STAGING. @MainActor private var stagingControls: some View { Group { - newModerationFlowToggle newOnboardingFlowToggle } } @@ -334,7 +320,6 @@ extension SettingsView { /// Controls that will appear when the app is built for DEBUG. @MainActor private var debugControls: some View { Group { - newModerationFlowToggle newOnboardingFlowToggle Text("sampleDataInstructions") .foregroundColor(.primaryTxt) diff --git a/NosTests/Service/MockFeatureFlags.swift b/NosTests/Service/MockFeatureFlags.swift index d64fc6a2e..4380be639 100644 --- a/NosTests/Service/MockFeatureFlags.swift +++ b/NosTests/Service/MockFeatureFlags.swift @@ -2,7 +2,6 @@ class MockFeatureFlags: FeatureFlags { /// Mock feature flags and their values. private var featureFlags: [FeatureFlag: Bool] = [ - .newModerationFlow: false, .newOnboardingFlow: true ]