Hide statuses without alt text

This commit is contained in:
Marcin Czachurski 2023-10-10 13:30:53 +02:00
parent 51b05ae128
commit 6a8bc9ef64
19 changed files with 267 additions and 26 deletions

View File

@ -20,6 +20,11 @@ public extension Status {
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 {
// If status is rebloged the we have to check if orginal status contains image.

View File

@ -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

View File

@ -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)

View File

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Vernissage-018.xcdatamodel</string>
<string>Vernissage-019.xcdatamodel</string>
</dict>
</plist>

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22222" systemVersion="23A344" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AccountData" representedClassName="AccountData" syncable="YES">
<attribute name="accessToken" optional="YES" attributeType="String"/>
<attribute name="acct" attributeType="String"/>
<attribute name="avatar" optional="YES" attributeType="URI"/>
<attribute name="avatarData" optional="YES" attributeType="Binary"/>
<attribute name="clientId" attributeType="String"/>
<attribute name="clientSecret" attributeType="String"/>
<attribute name="clientVapidKey" attributeType="String"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
<attribute name="followersCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="followingCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="header" optional="YES" attributeType="URI"/>
<attribute name="id" attributeType="String"/>
<attribute name="lastSeenStatusId" optional="YES" attributeType="String"/>
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="refreshToken" optional="YES" attributeType="String"/>
<attribute name="serverUrl" attributeType="URI"/>
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="username" attributeType="String"/>
<relationship name="accountRelationships" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AccountRelationship" inverseName="pixelfedAccount" inverseEntity="AccountRelationship"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="StatusData" inverseName="pixelfedAccount" inverseEntity="StatusData"/>
<relationship name="viewedStatuses" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="ViewedStatus" inverseName="pixelfedAccount" inverseEntity="ViewedStatus"/>
</entity>
<entity name="AccountRelationship" representedClassName="AccountRelationship" syncable="YES">
<attribute name="accountId" attributeType="String"/>
<attribute name="boostedStatusesMuted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="pixelfedAccount" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AccountData" inverseName="accountRelationships" inverseEntity="AccountData"/>
</entity>
<entity name="ApplicationSettings" representedClassName="ApplicationSettings" syncable="YES">
<attribute name="activeIcon" attributeType="String" defaultValueString="Default"/>
<attribute name="avatarShape" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="currentAccount" optional="YES" attributeType="String"/>
<attribute name="customNavigationMenuItem1" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="customNavigationMenuItem2" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
<attribute name="customNavigationMenuItem3" attributeType="Integer 32" defaultValueString="5" usesScalarValueType="YES"/>
<attribute name="hapticAnimationEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticButtonPressEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticNotificationEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticRefreshEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticTabSelectionEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hideStatusesWithoutAlt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastRefreshTokens" attributeType="Date" defaultDateTimeInterval="694256400" usesScalarValueType="NO"/>
<attribute name="menuPosition" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="showAltIconOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showAvatarsOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showFavouritesOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showGridOnUserProfile" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showPhotoDescription" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showReboostedStatuses" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showSensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="theme" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tintColor" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
<attribute name="warnAboutMissingAlt" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
</entity>
<entity name="AttachmentData" representedClassName="AttachmentData" syncable="YES">
<attribute name="blurhash" optional="YES" attributeType="String"/>
<attribute name="data" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
<attribute name="exifCamera" optional="YES" attributeType="String"/>
<attribute name="exifCreatedDate" optional="YES" attributeType="String"/>
<attribute name="exifExposure" optional="YES" attributeType="String"/>
<attribute name="exifLens" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="metaImageHeight" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="metaImageWidth" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="order" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="previewUrl" optional="YES" attributeType="URI"/>
<attribute name="remoteUrl" optional="YES" attributeType="URI"/>
<attribute name="statusId" attributeType="String"/>
<attribute name="text" optional="YES" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="url" attributeType="URI"/>
<relationship name="statusRelation" maxCount="1" deletionRule="Nullify" destinationEntity="StatusData" inverseName="attachmentsRelation" inverseEntity="StatusData"/>
</entity>
<entity name="StatusData" representedClassName="StatusData" syncable="YES">
<attribute name="accountAvatar" optional="YES" attributeType="URI"/>
<attribute name="accountDisplayName" optional="YES" attributeType="String"/>
<attribute name="accountId" attributeType="String"/>
<attribute name="accountUsername" optional="YES" attributeType="String"/>
<attribute name="applicationName" optional="YES" attributeType="String"/>
<attribute name="applicationWebsite" optional="YES" attributeType="URI"/>
<attribute name="bookmarked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="content" attributeType="String"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="favourited" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="favouritesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="String"/>
<attribute name="inReplyToAccount" optional="YES" attributeType="String"/>
<attribute name="inReplyToId" optional="YES" attributeType="String"/>
<attribute name="muted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="pinned" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="reblogged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="rebloggedAccountAvatar" optional="YES" attributeType="URI"/>
<attribute name="rebloggedAccountDisplayName" optional="YES" attributeType="String"/>
<attribute name="rebloggedAccountId" optional="YES" attributeType="String"/>
<attribute name="rebloggedAccountUsername" optional="YES" attributeType="String"/>
<attribute name="rebloggedStatusId" optional="YES" attributeType="String"/>
<attribute name="reblogsCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="repliesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="spoilerText" optional="YES" attributeType="String"/>
<attribute name="uri" attributeType="String"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="visibility" attributeType="String"/>
<relationship name="attachmentsRelation" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AttachmentData" inverseName="statusRelation" inverseEntity="AttachmentData"/>
<relationship name="pixelfedAccount" maxCount="1" deletionRule="Nullify" destinationEntity="AccountData" inverseName="statuses" inverseEntity="AccountData"/>
</entity>
<entity name="ViewedStatus" representedClassName="ViewedStatus" syncable="YES">
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" attributeType="String"/>
<attribute name="reblogId" optional="YES" attributeType="String"/>
<relationship name="pixelfedAccount" maxCount="1" deletionRule="Nullify" destinationEntity="AccountData" inverseName="viewedStatuses" inverseEntity="AccountData"/>
</entity>
</model>

View File

@ -110,6 +110,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
self.lastSeenStatusId = lastSeenStatusId

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -344,6 +344,7 @@
F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationOptions.swift; sourceTree = "<group>"; };
F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuItemDetails.swift; sourceTree = "<group>"; };
F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-011.xcdatamodel"; sourceTree = "<group>"; };
F87425F62AD5402700A119D7 /* Vernissage-019.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-019.xcdatamodel"; sourceTree = "<group>"; };
F8742FC329990AFB00E9642B /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
F8764186298ABB520057D362 /* ViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewState.swift; sourceTree = "<group>"; };
F876418C298AE5020057D362 /* PaginableStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginableStatusesView.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)
}
}
}
}

View File

@ -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
}
}

View File

@ -18,6 +18,7 @@ struct UserProfileHeaderView: View {
@State var account: Account
@ObservedObject var relationship = RelationshipModel()
@Binding var boostsDisabled: Bool
var body: some View {
VStack(alignment: .leading) {
@ -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")
@ -119,12 +120,17 @@ struct UserProfileHeaderView: View {
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)
}
}

View File

@ -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) {

View File

@ -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 {
@ -25,6 +27,12 @@ public struct TagWidget: View {
.font(.footnote)
}
if let image {
Image(image)
.foregroundColor(.white)
.font(.footnote)
}
Text(self.value, comment: "value")
.foregroundColor(.white)
.font(.footnote)