From 6a8bc9ef64148c3f4a57f43944efa85fc4cd3bfe Mon Sep 17 00:00:00 2001 From: Marcin Czachurski Date: Tue, 10 Oct 2023 13:30:53 +0200 Subject: [PATCH] Hide statuses without alt text --- .../Status+MediaAttachmentType.swift | 5 + ...plicationSettings+CoreDataProperties.swift | 1 + CoreData/ApplicationSettingsHandler.swift | 7 ++ .../Vernissage.xcdatamodeld/.xccurrentversion | 2 +- .../Vernissage-019.xcdatamodel/contents | 118 ++++++++++++++++++ .../EnvironmentKit/ApplicationState.swift | 3 + Localization/en.lproj/Localizable.strings | 3 + Localization/eu.lproj/Localizable.strings | 3 + Localization/fr.lproj/Localizable.strings | 3 + Localization/pl.lproj/Localizable.strings | 3 + Vernissage.xcodeproj/project.pbxproj | 16 +-- Vernissage/Services/HomeTimelineService.swift | 47 +++++-- Vernissage/VernissageApp.swift | 3 +- Vernissage/Views/HomeFeedView.swift | 16 ++- .../Subviews/MediaSettingsView.swift | 12 ++ Vernissage/Views/StatusesView.swift | 27 ++++ .../Subviews/UserProfileHeaderView.swift | 12 +- .../UserProfileView/UserProfileView.swift | 2 +- .../WidgetsKit/Widgets/TagWidget.swift | 10 +- 19 files changed, 267 insertions(+), 26 deletions(-) create mode 100644 CoreData/Vernissage.xcdatamodeld/Vernissage-019.xcdatamodel/contents diff --git a/ClientKit/Sources/ClientKit/Extensions/Status+MediaAttachmentType.swift b/ClientKit/Sources/ClientKit/Extensions/Status+MediaAttachmentType.swift index 7cce704..76b6c53 100644 --- a/ClientKit/Sources/ClientKit/Extensions/Status+MediaAttachmentType.swift +++ b/ClientKit/Sources/ClientKit/Extensions/Status+MediaAttachmentType.swift @@ -19,6 +19,11 @@ public extension Status { func statusContainsImage() -> Bool { return getAllImageMediaAttachments().isEmpty == false } + + func statusContainsAltText() -> Bool { + let mediaAttachments = self.getAllImageMediaAttachments() + return mediaAttachments.contains(where: { $0.description?.isEmpty == false }) + } func getAllImageMediaAttachments() -> [MediaAttachment] { if let reblog = self.reblog { diff --git a/CoreData/ApplicationSettings+CoreDataProperties.swift b/CoreData/ApplicationSettings+CoreDataProperties.swift index 1c9a977..77de39e 100644 --- a/CoreData/ApplicationSettings+CoreDataProperties.swift +++ b/CoreData/ApplicationSettings+CoreDataProperties.swift @@ -35,6 +35,7 @@ extension ApplicationSettings { @NSManaged public var warnAboutMissingAlt: Bool @NSManaged public var showGridOnUserProfile: Bool @NSManaged public var showReboostedStatuses: Bool + @NSManaged public var hideStatusesWithoutAlt: Bool @NSManaged public var customNavigationMenuItem1: Int32 @NSManaged public var customNavigationMenuItem2: Int32 diff --git a/CoreData/ApplicationSettingsHandler.swift b/CoreData/ApplicationSettingsHandler.swift index a2e5541..e23a3da 100644 --- a/CoreData/ApplicationSettingsHandler.swift +++ b/CoreData/ApplicationSettingsHandler.swift @@ -60,6 +60,7 @@ class ApplicationSettingsHandler { applicationState.warnAboutMissingAlt = defaultSettings.warnAboutMissingAlt applicationState.showGridOnUserProfile = defaultSettings.showGridOnUserProfile applicationState.showReboostedStatuses = defaultSettings.showReboostedStatuses + applicationState.hideStatusesWithoutAlt = defaultSettings.hideStatusesWithoutAlt if let menuPosition = MenuPosition(rawValue: Int(defaultSettings.menuPosition)) { applicationState.menuPosition = menuPosition @@ -204,6 +205,12 @@ class ApplicationSettingsHandler { CoreDataHandler.shared.save() } + func set(hideStatusesWithoutAlt: Bool) { + let defaultSettings = self.get() + defaultSettings.hideStatusesWithoutAlt = hideStatusesWithoutAlt + CoreDataHandler.shared.save() + } + private func createApplicationSettingsEntity(viewContext: NSManagedObjectContext? = nil) -> ApplicationSettings { let context = viewContext ?? CoreDataHandler.shared.container.viewContext return ApplicationSettings(context: context) diff --git a/CoreData/Vernissage.xcdatamodeld/.xccurrentversion b/CoreData/Vernissage.xcdatamodeld/.xccurrentversion index 3adfca2..91b43ba 100644 --- a/CoreData/Vernissage.xcdatamodeld/.xccurrentversion +++ b/CoreData/Vernissage.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Vernissage-018.xcdatamodel + Vernissage-019.xcdatamodel diff --git a/CoreData/Vernissage.xcdatamodeld/Vernissage-019.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage-019.xcdatamodel/contents new file mode 100644 index 0000000..51a0c56 --- /dev/null +++ b/CoreData/Vernissage.xcdatamodeld/Vernissage-019.xcdatamodel/contents @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift b/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift index 9049cef..40fd57e 100644 --- a/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift +++ b/EnvironmentKit/Sources/EnvironmentKit/ApplicationState.swift @@ -109,6 +109,9 @@ public class ApplicationState: ObservableObject { /// Show reboosted statuses on home timeline. @Published public var showReboostedStatuses = false + + /// Hide statuses without ALT text. + @Published public var hideStatusesWithoutAlt = false public func changeApplicationState(accountModel: AccountModel, instance: Instance?, lastSeenStatusId: String?) { self.account = accountModel diff --git a/Localization/en.lproj/Localizable.strings b/Localization/en.lproj/Localizable.strings index fc47004..283404a 100644 --- a/Localization/en.lproj/Localizable.strings +++ b/Localization/en.lproj/Localizable.strings @@ -130,6 +130,7 @@ "userProfile.title.blocked" = "Blocked"; "userProfile.title.enableBoosts" = "Enable boosts"; "userProfile.title.disableBoosts" = "Disable boosts"; +"userProfile.title.boostedStatusesMuted" = "Boosts muted"; // Mark: Notifications view. "notifications.navigationBar.title" = "Notifications"; @@ -244,6 +245,8 @@ "settings.title.warnAboutMissingAltDescription" = "A warning about missing ALT texts will be displayed before publishing new post."; "settings.title.enableReboostOnTimeline" = "Show boosted statuses"; "settings.title.enableReboostOnTimelineDescription" = "Boosted statuses will be visible on your home timeline."; +"settings.title.hideStatusesWithoutAlt" = "Hide statuses without ALT text"; +"settings.title.hideStatusesWithoutAltDescription" = "Statuses without ALT text will not be visible on your home timeline."; // Mark: Signin view. "signin.navigationBar.title" = "Sign in to Pixelfed"; diff --git a/Localization/eu.lproj/Localizable.strings b/Localization/eu.lproj/Localizable.strings index a66ce04..b2fa49c 100644 --- a/Localization/eu.lproj/Localizable.strings +++ b/Localization/eu.lproj/Localizable.strings @@ -130,6 +130,7 @@ "userProfile.title.blocked" = "Blokeatuta"; "userProfile.title.enableBoosts" = "Enable boosts"; "userProfile.title.disableBoosts" = "Disable boosts"; +"userProfile.title.boostedStatusesMuted" = "Boosts muted"; // Mark: Notifications view. "notifications.navigationBar.title" = "Jakinarazpenak"; @@ -244,6 +245,8 @@ "settings.title.warnAboutMissingAltDescription" = "Irudiren batek deskribapenik ez badu, argitaratu baino lehen abisua erakutsiko da."; "settings.title.enableReboostOnTimeline" = "Erakutsi bultzatutako egoerak"; "settings.title.enableReboostOnTimelineDescription" = "Besteek bultzatu dituzten egoerak zure denbora-lerroan erakutsiko dira."; +"settings.title.hideStatusesWithoutAlt" = "Hide statuses without ALT text"; +"settings.title.hideStatusesWithoutAltDescription" = "Statuses without ALT text will not be visible on your home timeline."; // Mark: Signin view. "signin.navigationBar.title" = "Hasi saioa Pixelfed-en"; diff --git a/Localization/fr.lproj/Localizable.strings b/Localization/fr.lproj/Localizable.strings index 64be7f7..fb6b47e 100644 --- a/Localization/fr.lproj/Localizable.strings +++ b/Localization/fr.lproj/Localizable.strings @@ -130,6 +130,7 @@ "userProfile.title.blocked" = "Bloquer"; "userProfile.title.enableBoosts" = "Enable boosts"; "userProfile.title.disableBoosts" = "Disable boosts"; +"userProfile.title.boostedStatusesMuted" = "Boosts muted"; // Mark: Notifications view. "notifications.navigationBar.title" = "Notifications"; @@ -244,6 +245,8 @@ "settings.title.warnAboutMissingAltDescription" = "Un avertissement concernant les textes ALT manquants sera affiché avant la publication d'un nouveau message."; "settings.title.enableReboostOnTimeline" = "Show boosted statuses"; "settings.title.enableReboostOnTimelineDescription" = "Boosted statuses will be visible on your home timeline."; +"settings.title.hideStatusesWithoutAlt" = "Hide statuses without ALT text"; +"settings.title.hideStatusesWithoutAltDescription" = "Statuses without ALT text will not be visible on your home timeline."; // Mark: Signin view. "signin.navigationBar.title" = "Se connecter à Pixelfed"; diff --git a/Localization/pl.lproj/Localizable.strings b/Localization/pl.lproj/Localizable.strings index e427bff..488ffce 100644 --- a/Localization/pl.lproj/Localizable.strings +++ b/Localization/pl.lproj/Localizable.strings @@ -130,6 +130,7 @@ "userProfile.title.blocked" = "Zablokowany"; "userProfile.title.enableBoosts" = "Wyświetl podbicia"; "userProfile.title.disableBoosts" = "Ukryj podbicia"; +"userProfile.title.boostedStatusesMuted" = "Podbicia ukryte"; // Mark: Notifications view. "notifications.navigationBar.title" = "Powiadomienia"; @@ -244,6 +245,8 @@ "settings.title.warnAboutMissingAltDescription" = "Ostrzeżenie o brakujących tekstach ALT będzie wyświetlane przed opublikowaniem nowego statusu."; "settings.title.enableReboostOnTimeline" = "Wyświetl podbite statusy"; "settings.title.enableReboostOnTimelineDescription" = "Podbite statusy będą widoczne na twojej osi czasu."; +"settings.title.hideStatusesWithoutAlt" = "Ukryj statusy bez tekstu ALT"; +"settings.title.hideStatusesWithoutAltDescription" = "Statusy bez tekstu ALT nie będą wyświetlane na twojej osi czasu."; // Mark: Signin view. "signin.navigationBar.title" = "Zaloguj się do Pixelfed"; diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 01c90ed..148e560 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -344,6 +344,7 @@ F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationOptions.swift; sourceTree = ""; }; F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuItemDetails.swift; sourceTree = ""; }; F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-011.xcdatamodel"; sourceTree = ""; }; + F87425F62AD5402700A119D7 /* Vernissage-019.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-019.xcdatamodel"; sourceTree = ""; }; F8742FC329990AFB00E9642B /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; F8764186298ABB520057D362 /* ViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewState.swift; sourceTree = ""; }; F876418C298AE5020057D362 /* PaginableStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginableStatusesView.swift; sourceTree = ""; }; @@ -1373,7 +1374,7 @@ CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 304; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageWidget/Info.plist; @@ -1404,7 +1405,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 304; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageWidget/Info.plist; @@ -1434,7 +1435,7 @@ CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 304; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageShare/Info.plist; @@ -1463,7 +1464,7 @@ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 304; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageShare/Info.plist; @@ -1617,7 +1618,7 @@ CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 304; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1660,7 +1661,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 304; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1857,6 +1858,7 @@ F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + F87425F62AD5402700A119D7 /* Vernissage-019.xcdatamodel */, F8E7ADF82AD44AFD0038FFFD /* Vernissage-018.xcdatamodel */, F8D8E0D22ACC89CB00AA1374 /* Vernissage-017.xcdatamodel */, F8BD04192ACC2280004B8E2C /* Vernissage-016.xcdatamodel */, @@ -1877,7 +1879,7 @@ F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */, F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */, ); - currentVersion = F8E7ADF82AD44AFD0038FFFD /* Vernissage-018.xcdatamodel */; + currentVersion = F87425F62AD5402700A119D7 /* Vernissage-019.xcdatamodel */; path = Vernissage.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Vernissage/Services/HomeTimelineService.swift b/Vernissage/Services/HomeTimelineService.swift index 1a3c057..ecdd45b 100644 --- a/Vernissage/Services/HomeTimelineService.swift +++ b/Vernissage/Services/HomeTimelineService.swift @@ -24,7 +24,7 @@ public class HomeTimelineService { private let semaphore = AsyncSemaphore(value: 1) @MainActor - public func loadOnBottom(for account: AccountModel, includeReblogs: Bool) async throws -> Int { + public func loadOnBottom(for account: AccountModel, includeReblogs: Bool, hideStatusesWithoutAlt: Bool) async throws -> Int { // Load data from API and operate on CoreData on background context. let backgroundContext = CoreDataHandler.shared.newBackgroundContext() @@ -36,7 +36,11 @@ public class HomeTimelineService { } // Load data on bottom of the list. - let allStatusesFromApi = try await self.load(for: account, includeReblogs: includeReblogs, on: backgroundContext, maxId: oldestStatus.id) + let allStatusesFromApi = try await self.load(for: account, + includeReblogs: includeReblogs, + hideStatusesWithoutAlt: hideStatusesWithoutAlt, + on: backgroundContext, + maxId: oldestStatus.id) // Save data into database. CoreDataHandler.shared.save(viewContext: backgroundContext) @@ -49,7 +53,7 @@ public class HomeTimelineService { } @MainActor - public func refreshTimeline(for account: AccountModel, includeReblogs: Bool, updateLastSeenStatus: Bool = false) async throws -> String? { + public func refreshTimeline(for account: AccountModel, includeReblogs: Bool, hideStatusesWithoutAlt: Bool, updateLastSeenStatus: Bool = false) async throws -> String? { await semaphore.wait() defer { semaphore.signal() } @@ -62,7 +66,10 @@ public class HomeTimelineService { // Refresh/load home timeline (refreshing on top downloads always first 40 items). // When Apple introduce good way to show new items without scroll to top then we can change that method. - let allStatusesFromApi = try await self.refresh(for: account, includeReblogs: includeReblogs, on: backgroundContext) + let allStatusesFromApi = try await self.refresh(for: account, + includeReblogs: includeReblogs, + hideStatusesWithoutAlt: hideStatusesWithoutAlt, + on: backgroundContext) // Update last seen status. if let lastSeenStatusId, updateLastSeenStatus == true { @@ -95,7 +102,7 @@ public class HomeTimelineService { CoreDataHandler.shared.save() } - public func amountOfNewStatuses(for account: AccountModel, includeReblogs: Bool) async -> Int { + public func amountOfNewStatuses(for account: AccountModel, includeReblogs: Bool, hideStatusesWithoutAlt: Bool) async -> Int { await semaphore.wait() defer { semaphore.signal() } @@ -131,6 +138,11 @@ public class HomeTimelineService { let statusesWithImagesOnly = downloadedStatuses.getStatusesWithImagesOnly() for status in statusesWithImagesOnly { + // We have to hide statuses without ALT text. + if hideStatusesWithoutAlt && status.statusContainsAltText() == false { + continue + } + // We shouldn't add statuses that are boosted by muted accounts. if AccountRelationshipHandler.shared.isBoostedStatusesMuted(accountId: account.id, status: status, viewContext: backgroundContext) { continue @@ -190,9 +202,12 @@ public class HomeTimelineService { return statusData } - private func refresh(for account: AccountModel, includeReblogs: Bool, on backgroundContext: NSManagedObjectContext) async throws -> [Status] { + private func refresh(for account: AccountModel, includeReblogs: Bool, hideStatusesWithoutAlt: Bool, on backgroundContext: NSManagedObjectContext) async throws -> [Status] { // Retrieve statuses from API. - let statuses = try await self.getUniqueStatusesForHomeTimeline(account: account, includeReblogs: includeReblogs, on: backgroundContext) + let statuses = try await self.getUniqueStatusesForHomeTimeline(account: account, + includeReblogs: includeReblogs, + hideStatusesWithoutAlt: hideStatusesWithoutAlt, + on: backgroundContext) // Update all existing statuses in database. for status in statuses { @@ -246,11 +261,16 @@ public class HomeTimelineService { private func load(for account: AccountModel, includeReblogs: Bool, + hideStatusesWithoutAlt: Bool, on backgroundContext: NSManagedObjectContext, maxId: String? = nil ) async throws -> [Status] { // Retrieve statuses from API. - let statuses = try await self.getUniqueStatusesForHomeTimeline(account: account, maxId: maxId, includeReblogs: includeReblogs, on: backgroundContext) + let statuses = try await self.getUniqueStatusesForHomeTimeline(account: account, + maxId: maxId, + includeReblogs: includeReblogs, + hideStatusesWithoutAlt: hideStatusesWithoutAlt, + on: backgroundContext) // Save statuses in database. try await self.add(statuses, for: account, on: backgroundContext) @@ -348,7 +368,11 @@ public class HomeTimelineService { return ViewedStatusHandler.shared.hasBeenAlreadyOnTimeline(accountId: accountId, status: status, viewContext: backgroundContext) } - private func getUniqueStatusesForHomeTimeline(account: AccountModel, maxId: EntityId? = nil, includeReblogs: Bool? = nil, on backgroundContext: NSManagedObjectContext) async throws -> [Status] { + private func getUniqueStatusesForHomeTimeline(account: AccountModel, + maxId: EntityId? = nil, + includeReblogs: Bool? = nil, + hideStatusesWithoutAlt: Bool = false, + on backgroundContext: NSManagedObjectContext) async throws -> [Status] { guard let accessToken = account.accessToken else { return [] } @@ -371,6 +395,11 @@ public class HomeTimelineService { let statusesWithImagesOnly = downloadedStatuses.getStatusesWithImagesOnly() for status in statusesWithImagesOnly { + // We have to hide statuses without ALT text. + if hideStatusesWithoutAlt && status.statusContainsAltText() == false { + continue + } + // We shouldn't add statuses that are boosted by muted accounts. if AccountRelationshipHandler.shared.isBoostedStatusesMuted(accountId: account.id, status: status, viewContext: backgroundContext) { continue diff --git a/Vernissage/VernissageApp.swift b/Vernissage/VernissageApp.swift index 75a7d49..4517ea8 100644 --- a/Vernissage/VernissageApp.swift +++ b/Vernissage/VernissageApp.swift @@ -205,7 +205,8 @@ struct VernissageApp: App { private func calculateNewPhotosInBackground() async { if let account = self.applicationState.account { self.applicationState.amountOfNewStatuses = await HomeTimelineService.shared.amountOfNewStatuses(for: account, - includeReblogs: self.applicationState.showReboostedStatuses) + includeReblogs: self.applicationState.showReboostedStatuses, + hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt) } } } diff --git a/Vernissage/Views/HomeFeedView.swift b/Vernissage/Views/HomeFeedView.swift index beaf707..9ce2d74 100644 --- a/Vernissage/Views/HomeFeedView.swift +++ b/Vernissage/Views/HomeFeedView.swift @@ -71,7 +71,12 @@ struct HomeFeedView: View { .task { do { if let account = self.applicationState.account { - let newStatusesCount = try await HomeTimelineService.shared.loadOnBottom(for: account, includeReblogs: self.applicationState.showReboostedStatuses) + let newStatusesCount = try await HomeTimelineService.shared.loadOnBottom( + for: account, + includeReblogs: self.applicationState.showReboostedStatuses, + hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt + ) + if newStatusesCount == 0 { allItemsLoaded = true } @@ -103,7 +108,10 @@ struct HomeFeedView: View { private func refreshData() async { do { if let account = self.applicationState.account { - let lastSeenStatusId = try await HomeTimelineService.shared.refreshTimeline(for: account, includeReblogs: self.applicationState.showReboostedStatuses, updateLastSeenStatus: true) + let lastSeenStatusId = try await HomeTimelineService.shared.refreshTimeline(for: account, + includeReblogs: self.applicationState.showReboostedStatuses, + hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt, + updateLastSeenStatus: true) asyncAfter(0.75) { self.applicationState.lastSeenStatusId = lastSeenStatusId self.applicationState.amountOfNewStatuses = 0 @@ -126,7 +134,9 @@ struct HomeFeedView: View { } if let account = self.applicationState.account { - _ = try await HomeTimelineService.shared.refreshTimeline(for: account, includeReblogs: self.applicationState.showReboostedStatuses) + _ = try await HomeTimelineService.shared.refreshTimeline(for: account, + includeReblogs: self.applicationState.showReboostedStatuses, + hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt) } self.applicationState.amountOfNewStatuses = 0 diff --git a/Vernissage/Views/SettingsView/Subviews/MediaSettingsView.swift b/Vernissage/Views/SettingsView/Subviews/MediaSettingsView.swift index 8fb41dc..a777f04 100644 --- a/Vernissage/Views/SettingsView/Subviews/MediaSettingsView.swift +++ b/Vernissage/Views/SettingsView/Subviews/MediaSettingsView.swift @@ -97,6 +97,18 @@ struct MediaSettingsView: View { .onChange(of: self.applicationState.showReboostedStatuses) { newValue in ApplicationSettingsHandler.shared.set(showReboostedStatuses: newValue) } + + Toggle(isOn: $applicationState.hideStatusesWithoutAlt) { + VStack(alignment: .leading) { + Text("settings.title.hideStatusesWithoutAlt", comment: "Hide statuses without ALT") + Text("settings.title.hideStatusesWithoutAltDescription", comment: "Statuses without ALT text will not be visible on your home timeline.") + .font(.footnote) + .foregroundColor(.customGrayColor) + } + } + .onChange(of: self.applicationState.hideStatusesWithoutAlt) { newValue in + ApplicationSettingsHandler.shared.set(hideStatusesWithoutAlt: newValue) + } } } } diff --git a/Vernissage/Views/StatusesView.swift b/Vernissage/Views/StatusesView.swift index 51bb99f..4d82f12 100644 --- a/Vernissage/Views/StatusesView.swift +++ b/Vernissage/Views/StatusesView.swift @@ -185,6 +185,11 @@ struct StatusesView: View { // Get only statuses with images. var inPlaceStatuses: [StatusModel] = [] for item in statuses.getStatusesWithImagesOnly() { + // We have to hide statuses without ALT text. + if self.shouldHideStatusWithoutAlt(status: item) { + continue + } + // We have to skip statuses that are boosted from muted accounts. if let accountId = self.applicationState.account?.id, AccountRelationshipHandler.shared.isBoostedStatusesMuted(accountId: accountId, status: item) { continue @@ -217,6 +222,11 @@ struct StatusesView: View { // Get only statuses with images. var inPlaceStatuses: [StatusModel] = [] for item in previousStatuses.getStatusesWithImagesOnly() { + // We have to hide statuses without ALT text. + if self.shouldHideStatusWithoutAlt(status: item) { + continue + } + // We have to skip statuses that are boosted from muted accounts. if let accountId = self.applicationState.account?.id, AccountRelationshipHandler.shared.isBoostedStatusesMuted(accountId: accountId, status: item) { continue @@ -247,6 +257,11 @@ struct StatusesView: View { // Get only statuses with images. var inPlaceStatuses: [StatusModel] = [] for item in statuses.getStatusesWithImagesOnly() { + // We have to hide statuses without ALT text. + if self.shouldHideStatusWithoutAlt(status: item) { + continue + } + // We have to skip statuses that are boosted from muted accounts. if let accountId = self.applicationState.account?.id, AccountRelationshipHandler.shared.isBoostedStatusesMuted(accountId: accountId, status: item) { continue @@ -366,4 +381,16 @@ struct StatusesView: View { private func prefetch(statusModels: [StatusModel]) { imagePrefetcher.startPrefetching(with: statusModels.getAllImagesUrls()) } + + private func shouldHideStatusWithoutAlt(status: Status) -> Bool { + if self.applicationState.hideStatusesWithoutAlt == false { + return false + } + + if self.listType != .home && self.listType != .local && self.listType != .federated { + return false + } + + return status.statusContainsAltText() == false + } } diff --git a/Vernissage/Views/UserProfileView/Subviews/UserProfileHeaderView.swift b/Vernissage/Views/UserProfileView/Subviews/UserProfileHeaderView.swift index e5030eb..b81119f 100644 --- a/Vernissage/Views/UserProfileView/Subviews/UserProfileHeaderView.swift +++ b/Vernissage/Views/UserProfileView/Subviews/UserProfileHeaderView.swift @@ -18,7 +18,8 @@ struct UserProfileHeaderView: View { @State var account: Account @ObservedObject var relationship = RelationshipModel() - + @Binding var boostsDisabled: Bool + var body: some View { VStack(alignment: .leading) { HStack(alignment: .center) { @@ -109,7 +110,7 @@ struct UserProfileHeaderView: View { @ViewBuilder private func accountRelationshipPanel() -> some View { - if self.relationship.followedBy || self.relationship.muting || self.relationship.blocking { + if self.relationship.followedBy || self.relationship.muting || self.relationship.blocking || self.boostsDisabled { HStack(alignment: .top) { if self.relationship.followedBy { TagWidget(value: "userProfile.title.followsYou", color: .secondary, systemImage: "person.crop.circle.badge.checkmark") @@ -118,13 +119,18 @@ struct UserProfileHeaderView: View { if self.relationship.muting { TagWidget(value: "userProfile.title.muted", color: .accentColor, systemImage: "message.and.waveform.fill") } + + if self.boostsDisabled { + TagWidget(value: "userProfile.title.boostedStatusesMuted", color: .accentColor, image: "custom.rocket.fill") + } if self.relationship.blocking { TagWidget(value: "userProfile.title.blocked", color: .dangerColor, systemImage: "hand.raised.fill") } - + Spacer() } + .transition(.opacity) } } diff --git a/Vernissage/Views/UserProfileView/UserProfileView.swift b/Vernissage/Views/UserProfileView/UserProfileView.swift index 8587a2a..e11dd58 100644 --- a/Vernissage/Views/UserProfileView/UserProfileView.swift +++ b/Vernissage/Views/UserProfileView/UserProfileView.swift @@ -70,7 +70,7 @@ struct UserProfileView: View { private func accountView(account: Account) -> some View { ScrollView { - UserProfileHeaderView(account: account, relationship: relationship) + UserProfileHeaderView(account: account, relationship: relationship, boostsDisabled: $boostsDisabled) .id(self.viewId) if self.applicationState.account?.id == account.id || self.relationship.haveAccessToPhotos(account: account) { diff --git a/WidgetsKit/Sources/WidgetsKit/Widgets/TagWidget.swift b/WidgetsKit/Sources/WidgetsKit/Widgets/TagWidget.swift index 0f33276..da1aefc 100644 --- a/WidgetsKit/Sources/WidgetsKit/Widgets/TagWidget.swift +++ b/WidgetsKit/Sources/WidgetsKit/Widgets/TagWidget.swift @@ -10,11 +10,13 @@ public struct TagWidget: View { private let value: LocalizedStringKey private let color: Color private let systemImage: String? + private let image: String? - public init(value: LocalizedStringKey, color: Color, systemImage: String? = nil) { + public init(value: LocalizedStringKey, color: Color, systemImage: String? = nil, image: String? = nil) { self.value = value self.color = color self.systemImage = systemImage + self.image = image } public var body: some View { @@ -24,6 +26,12 @@ public struct TagWidget: View { .foregroundColor(.white) .font(.footnote) } + + if let image { + Image(image) + .foregroundColor(.white) + .font(.footnote) + } Text(self.value, comment: "value") .foregroundColor(.white)