Code cleanup / format / lint

This commit is contained in:
Thomas Ricouard 2023-01-27 20:36:40 +01:00
parent 8cac9df8c6
commit b89221a535
29 changed files with 239 additions and 262 deletions

View file

@ -5,100 +5,100 @@
// Created by Thomas Durand on 26/01/2023. // Created by Thomas Durand on 26/01/2023.
// //
import UIKit
import MobileCoreServices import MobileCoreServices
import UIKit
import UniformTypeIdentifiers import UniformTypeIdentifiers
import Models import Models
import Network import Network
// Sample code was sending this from a thread to another, let asume @Sendable for this // Sample code was sending this from a thread to another, let asume @Sendable for this
extension NSExtensionContext: @unchecked Sendable { } extension NSExtensionContext: @unchecked Sendable {}
class ActionRequestHandler: NSObject, NSExtensionRequestHandling { class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
enum Error: Swift.Error { enum Error: Swift.Error {
case inputProviderNotFound case inputProviderNotFound
case loadedItemHasWrongType case loadedItemHasWrongType
case urlNotFound case urlNotFound
case noHost case noHost
case notMastodonInstance case notMastodonInstance
} }
func beginRequest(with context: NSExtensionContext) { func beginRequest(with context: NSExtensionContext) {
// Do not call super in an Action extension with no user interface // Do not call super in an Action extension with no user interface
Task { Task {
do { do {
let url = try await url(from: context) let url = try await url(from: context)
guard await url.isMastodonInstance else { guard await url.isMastodonInstance else {
throw Error.notMastodonInstance throw Error.notMastodonInstance
}
await MainActor.run {
let deeplink = url.iceCubesAppDeepLink
let output = output(wrapping: deeplink)
context.completeRequest(returningItems: output)
}
} catch {
await MainActor.run {
context.completeRequest(returningItems: [])
}
}
} }
await MainActor.run {
let deeplink = url.iceCubesAppDeepLink
let output = output(wrapping: deeplink)
context.completeRequest(returningItems: output)
}
} catch {
await MainActor.run {
context.completeRequest(returningItems: [])
}
}
} }
}
} }
extension URL { extension URL {
var isMastodonInstance: Bool { var isMastodonInstance: Bool {
get async { get async {
do { do {
guard let host = host() else { guard let host = host() else {
throw ActionRequestHandler.Error.noHost throw ActionRequestHandler.Error.noHost
}
let _: Instance = try await Client(server: host).get(endpoint: Instances.instance)
return true
} catch {
return false
}
} }
let _: Instance = try await Client(server: host).get(endpoint: Instances.instance)
return true
} catch {
return false
}
} }
}
var iceCubesAppDeepLink: URL { var iceCubesAppDeepLink: URL {
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)! var components = URLComponents(url: self, resolvingAgainstBaseURL: false)!
components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"]) components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"])
return components.url! return components.url!
} }
} }
extension ActionRequestHandler { extension ActionRequestHandler {
/// Will look for an input item that might provide the property list that Javascript sent us /// Will look for an input item that might provide the property list that Javascript sent us
private func url(from context: NSExtensionContext) async throws -> URL { private func url(from context: NSExtensionContext) async throws -> URL {
for item in context.inputItems as! [NSExtensionItem] { for item in context.inputItems as! [NSExtensionItem] {
guard let attachments = item.attachments else { guard let attachments = item.attachments else {
continue continue
} }
for itemProvider in attachments { for itemProvider in attachments {
guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else { guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else {
continue continue
}
guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else {
throw Error.loadedItemHasWrongType
}
let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:]
guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else {
throw Error.urlNotFound
}
return url
}
} }
throw Error.inputProviderNotFound guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else {
throw Error.loadedItemHasWrongType
}
let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:]
guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else {
throw Error.urlNotFound
}
return url
}
} }
throw Error.inputProviderNotFound
}
/// Wrap the output to the expected object so we send back results to JS /// Wrap the output to the expected object so we send back results to JS
private func output(wrapping deeplink: URL) -> [NSExtensionItem] { private func output(wrapping deeplink: URL) -> [NSExtensionItem] {
let results = ["deeplink": deeplink.absoluteString] let results = ["deeplink": deeplink.absoluteString]
let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results] let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results]
let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier) let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier)
let item = NSExtensionItem() let item = NSExtensionItem()
item.attachments = [provider] item.attachments = [provider]
return [item] return [item]
} }
} }

View file

@ -2,8 +2,8 @@ import Account
import AppAccount import AppAccount
import DesignSystem import DesignSystem
import Env import Env
import SwiftUI
import Models import Models
import SwiftUI
struct SideBarView<Content: View>: View { struct SideBarView<Content: View>: View {
@EnvironmentObject private var appAccounts: AppAccountsManager @EnvironmentObject private var appAccounts: AppAccountsManager

View file

@ -1,25 +1,25 @@
import SwiftUI
import Account import Account
import AppAccount
import DesignSystem import DesignSystem
import Env import Env
import Models import Models
import AppAccount import SwiftUI
struct AccountSettingsView: View { struct AccountSettingsView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject private var pushNotifications: PushNotificationsService @EnvironmentObject private var pushNotifications: PushNotificationsService
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var currentInstance: CurrentInstance @EnvironmentObject private var currentInstance: CurrentInstance
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var appAccountsManager: AppAccountsManager @EnvironmentObject private var appAccountsManager: AppAccountsManager
@State private var isEditingAccount: Bool = false @State private var isEditingAccount: Bool = false
@State private var isEditingFilters: Bool = false @State private var isEditingFilters: Bool = false
let account: Account let account: Account
let appAccount: AppAccount let appAccount: AppAccount
var body: some View { var body: some View {
Form { Form {
Section { Section {
@ -54,7 +54,6 @@ struct AccountSettingsView: View {
} label: { } label: {
Text("account.action.logout") Text("account.action.logout")
} }
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }

View file

@ -8,10 +8,9 @@ import SwiftUI
import UserNotifications import UserNotifications
struct ContentSettingsView: View { struct ContentSettingsView: View {
@EnvironmentObject private var userPreferences: UserPreferences @EnvironmentObject private var userPreferences: UserPreferences
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
var body: some View { var body: some View {
Form { Form {
Section { Section {
@ -19,7 +18,7 @@ struct ContentSettingsView: View {
Text("settings.content.use-instance-settings") Text("settings.content.use-instance-settings")
} }
} footer: { } footer: {
Text("settings.content.main-toggle.description") Text("settings.content.main-toggle.description")
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
.onChange(of: userPreferences.useInstanceContentSettings) { newVal in .onChange(of: userPreferences.useInstanceContentSettings) { newVal in
@ -29,15 +28,14 @@ struct ContentSettingsView: View {
userPreferences.appDefaultPostsSensitive = userPreferences.postIsSensitive userPreferences.appDefaultPostsSensitive = userPreferences.postIsSensitive
userPreferences.appDefaultPostVisibility = userPreferences.postVisibility userPreferences.appDefaultPostVisibility = userPreferences.postVisibility
} }
} }
Section("settings.content.reading") { Section("settings.content.reading") {
Toggle(isOn: $userPreferences.appAutoExpandSpoilers) { Toggle(isOn: $userPreferences.appAutoExpandSpoilers) {
Text("settings.content.expand-spoilers") Text("settings.content.expand-spoilers")
} }
.disabled(userPreferences.useInstanceContentSettings) .disabled(userPreferences.useInstanceContentSettings)
Picker("settings.content.expand-media", selection: $userPreferences.appAutoExpandMedia) { Picker("settings.content.expand-media", selection: $userPreferences.appAutoExpandMedia) {
ForEach(ServerPreferences.AutoExpandMedia.allCases, id: \.rawValue) { media in ForEach(ServerPreferences.AutoExpandMedia.allCases, id: \.rawValue) { media in
Text(media.description).tag(media) Text(media.description).tag(media)
@ -45,7 +43,7 @@ struct ContentSettingsView: View {
} }
.disabled(userPreferences.useInstanceContentSettings) .disabled(userPreferences.useInstanceContentSettings)
}.listRowBackground(theme.primaryBackgroundColor) }.listRowBackground(theme.primaryBackgroundColor)
Section("settings.content.posting") { Section("settings.content.posting") {
Picker("settings.content.default-visibility", selection: $userPreferences.appDefaultPostVisibility) { Picker("settings.content.default-visibility", selection: $userPreferences.appDefaultPostVisibility) {
ForEach(Visibility.allCases, id: \.rawValue) { vis in ForEach(Visibility.allCases, id: \.rawValue) { vis in
@ -53,23 +51,16 @@ struct ContentSettingsView: View {
} }
} }
.disabled(userPreferences.useInstanceContentSettings) .disabled(userPreferences.useInstanceContentSettings)
Toggle(isOn: $userPreferences.appDefaultPostsSensitive) { Toggle(isOn: $userPreferences.appDefaultPostsSensitive) {
Text("settings.content.default-sensitive") Text("settings.content.default-sensitive")
} }
.disabled(userPreferences.useInstanceContentSettings) .disabled(userPreferences.useInstanceContentSettings)
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
.navigationTitle("settings.content.navigation-title") .navigationTitle("settings.content.navigation-title")
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
} }
} }

View file

@ -33,7 +33,7 @@ struct PushNotificationsView: View {
Text("settings.push.main-toggle.description") Text("settings.push.main-toggle.description")
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
if subscription.isEnabled { if subscription.isEnabled {
Section { Section {
Toggle(isOn: .init(get: { Toggle(isOn: .init(get: {
@ -87,7 +87,7 @@ struct PushNotificationsView: View {
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
Section { Section {
Button("settings.push.duplicate.button.fix") { Button("settings.push.duplicate.button.fix") {
Task { Task {
@ -101,7 +101,6 @@ struct PushNotificationsView: View {
Text("settings.push.duplicate.footer") Text("settings.push.duplicate.footer")
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
.navigationTitle("settings.push.navigation-title") .navigationTitle("settings.push.navigation-title")
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
@ -116,7 +115,7 @@ struct PushNotificationsView: View {
await subscription.updateSubscription() await subscription.updateSubscription()
} }
} }
private func deleteSubscription() { private func deleteSubscription() {
Task { Task {
await subscription.deleteSubscription() await subscription.deleteSubscription()

View file

@ -10,7 +10,7 @@ import Timeline
struct SettingsTabs: View { struct SettingsTabs: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject private var pushNotifications: PushNotificationsService @EnvironmentObject private var pushNotifications: PushNotificationsService
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@ -75,7 +75,8 @@ struct SettingsTabs: View {
if let index = indexSet.first { if let index = indexSet.first {
let account = appAccountsManager.availableAccounts[index] let account = appAccountsManager.availableAccounts[index]
if let token = account.oauthToken, if let token = account.oauthToken,
let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token }) { let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token })
{
Task { Task {
await sub.deleteSubscription() await sub.deleteSubscription()
appAccountsManager.delete(account: account) appAccountsManager.delete(account: account)

View file

@ -25,7 +25,7 @@ public struct AccountDetailView: View {
@State private var isCurrentUser: Bool = false @State private var isCurrentUser: Bool = false
@State private var isCreateListAlertPresented: Bool = false @State private var isCreateListAlertPresented: Bool = false
@State private var createListTitle: String = "" @State private var createListTitle: String = ""
@State private var isEditingAccount: Bool = false @State private var isEditingAccount: Bool = false
@State private var isEditingFilters: Bool = false @State private var isEditingFilters: Bool = false
@ -513,7 +513,7 @@ public struct AccountDetailView: View {
} label: { } label: {
Label("account.action.edit-info", systemImage: "pencil") Label("account.action.edit-info", systemImage: "pencil")
} }
if curretnInstance.isFiltersSupported { if curretnInstance.isFiltersSupported {
Button { Button {
isEditingFilters = true isEditingFilters = true
@ -521,7 +521,7 @@ public struct AccountDetailView: View {
Label("account.action.edit-filters", systemImage: "line.3.horizontal.decrease.circle") Label("account.action.edit-filters", systemImage: "line.3.horizontal.decrease.circle")
} }
} }
Button { Button {
routerPath.presentedSheet = .accountPushNotficationsSettings routerPath.presentedSheet = .accountPushNotficationsSettings
} label: { } label: {

View file

@ -9,8 +9,8 @@ public struct EditAccountView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@StateObject private var viewModel = EditAccountViewModel() @StateObject private var viewModel = EditAccountViewModel()
public init() { } public init() {}
public var body: some View { public var body: some View {
NavigationStack { NavigationStack {

View file

@ -1,16 +1,16 @@
import SwiftUI
import Models
import Env
import DesignSystem import DesignSystem
import Env
import Models
import Network import Network
import SwiftUI
struct EditFilterView: View { struct EditFilterView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var account: CurrentAccount @EnvironmentObject private var account: CurrentAccount
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@State private var isSavingFilter: Bool = false @State private var isSavingFilter: Bool = false
@State private var filter: ServerFilter? @State private var filter: ServerFilter?
@State private var title: String @State private var title: String
@ -18,20 +18,20 @@ struct EditFilterView: View {
@State private var newKeyword: String = "" @State private var newKeyword: String = ""
@State private var contexts: [ServerFilter.Context] @State private var contexts: [ServerFilter.Context]
@State private var filterAction: ServerFilter.Action @State private var filterAction: ServerFilter.Action
@FocusState private var isTitleFocused: Bool @FocusState private var isTitleFocused: Bool
private var data: ServerFilterData { private var data: ServerFilterData {
.init(title: title, .init(title: title,
context: contexts, context: contexts,
filterAction: filterAction, filterAction: filterAction,
expireIn: nil) expireIn: nil)
} }
private var canSave: Bool { private var canSave: Bool {
!title.isEmpty !title.isEmpty
} }
init(filter: ServerFilter?) { init(filter: ServerFilter?) {
_filter = .init(initialValue: filter) _filter = .init(initialValue: filter)
_title = .init(initialValue: filter?.title ?? "") _title = .init(initialValue: filter?.title ?? "")
@ -39,7 +39,7 @@ struct EditFilterView: View {
_contexts = .init(initialValue: filter?.context ?? [.home]) _contexts = .init(initialValue: filter?.context ?? [.home])
_filterAction = .init(initialValue: filter?.filterAction ?? .warn) _filterAction = .init(initialValue: filter?.filterAction ?? .warn)
} }
var body: some View { var body: some View {
Form { Form {
titleSection titleSection
@ -55,7 +55,7 @@ struct EditFilterView: View {
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.onAppear { .onAppear {
if filter == nil { if filter == nil {
isTitleFocused = true isTitleFocused = true
} }
} }
.toolbar { .toolbar {
@ -64,7 +64,7 @@ struct EditFilterView: View {
} }
} }
} }
private var titleSection: some View { private var titleSection: some View {
Section("filter.edit.title") { Section("filter.edit.title") {
TextField("filter.edit.title", text: $title) TextField("filter.edit.title", text: $title)
@ -76,9 +76,8 @@ struct EditFilterView: View {
} }
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
private var keywordsSection: some View { private var keywordsSection: some View {
Section("filter.edit.keywords") { Section("filter.edit.keywords") {
ForEach(keywords) { keyword in ForEach(keywords) { keyword in
@ -113,7 +112,7 @@ struct EditFilterView: View {
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
private var contextsSection: some View { private var contextsSection: some View {
Section("filter.edit.contexts") { Section("filter.edit.contexts") {
ForEach(ServerFilter.Context.allCases, id: \.self) { context in ForEach(ServerFilter.Context.allCases, id: \.self) { context in
@ -136,7 +135,7 @@ struct EditFilterView: View {
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
} }
private var filterActionView: some View { private var filterActionView: some View {
Section("filter.edit.action") { Section("filter.edit.action") {
Picker(selection: $filterAction) { Picker(selection: $filterAction) {
@ -156,7 +155,7 @@ struct EditFilterView: View {
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
private var saveButton: some View { private var saveButton: some View {
Button { Button {
Task { Task {
@ -172,7 +171,7 @@ struct EditFilterView: View {
} }
.disabled(!canSave) .disabled(!canSave)
} }
private func saveFilter() async { private func saveFilter() async {
do { do {
isSavingFilter = true isSavingFilter = true
@ -182,26 +181,26 @@ struct EditFilterView: View {
} else { } else {
let newFilter: ServerFilter = try await client.post(endpoint: ServerFilters.createFilter(json: data), let newFilter: ServerFilter = try await client.post(endpoint: ServerFilters.createFilter(json: data),
forceVersion: .v2) forceVersion: .v2)
self.filter = newFilter filter = newFilter
} }
} catch {} } catch {}
isSavingFilter = false isSavingFilter = false
} }
private func addKeyword(name: String) async { private func addKeyword(name: String) async {
guard let filterId = filter?.id else { return } guard let filterId = filter?.id else { return }
isSavingFilter = true isSavingFilter = true
do { do {
let keyword: ServerFilter.Keyword = try await let keyword: ServerFilter.Keyword = try await
client.post(endpoint: ServerFilters.addKeyword(filter: filterId, client.post(endpoint: ServerFilters.addKeyword(filter: filterId,
keyword: name, keyword: name,
wholeWord: true), wholeWord: true),
forceVersion: .v2) forceVersion: .v2)
self.keywords.append(keyword) keywords.append(keyword)
} catch { } } catch {}
isSavingFilter = false isSavingFilter = false
} }
private func deleteKeyword(keyword: ServerFilter.Keyword) async { private func deleteKeyword(keyword: ServerFilter.Keyword) async {
isSavingFilter = true isSavingFilter = true
do { do {
@ -210,7 +209,7 @@ struct EditFilterView: View {
if response?.statusCode == 200 { if response?.statusCode == 200 {
keywords.removeAll(where: { $0.id == keyword.id }) keywords.removeAll(where: { $0.id == keyword.id })
} }
} catch { } } catch {}
isSavingFilter = false isSavingFilter = false
} }
} }

View file

@ -1,21 +1,21 @@
import SwiftUI
import Env
import Network
import DesignSystem import DesignSystem
import Env
import Models import Models
import Network
import SwiftUI
public struct FiltersListView: View { public struct FiltersListView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var account: CurrentAccount @EnvironmentObject private var account: CurrentAccount
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@State private var isLoading: Bool = true @State private var isLoading: Bool = true
@State private var filters: [ServerFilter] = [] @State private var filters: [ServerFilter] = []
public init() { } public init() {}
public var body: some View { public var body: some View {
NavigationStack { NavigationStack {
Form { Form {
@ -31,7 +31,7 @@ public struct FiltersListView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(filter.title) Text(filter.title)
.font(.scaledSubheadline) .font(.scaledSubheadline)
Text("\(filter.context.map{ $0.name }.joined(separator: ", "))") Text("\(filter.context.map { $0.name }.joined(separator: ", "))")
.font(.scaledBody) .font(.scaledBody)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
@ -44,7 +44,7 @@ public struct FiltersListView: View {
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
Section { Section {
NavigationLink(destination: EditFilterView(filter: nil)) { NavigationLink(destination: EditFilterView(filter: nil)) {
Label("filter.new", systemImage: "plus") Label("filter.new", systemImage: "plus")
@ -70,7 +70,7 @@ public struct FiltersListView: View {
} }
} }
} }
private func deleteFilter(indexes: IndexSet) { private func deleteFilter(indexes: IndexSet) {
if let index = indexes.first { if let index = indexes.first {
Task { Task {
@ -84,7 +84,7 @@ public struct FiltersListView: View {
} }
} }
} }
@ToolbarContentBuilder @ToolbarContentBuilder
private var toolbarContent: some ToolbarContent { private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {

View file

@ -4,7 +4,7 @@ import Models
import Network import Network
import SwiftUI import SwiftUI
extension AppAccount { public extension AppAccount {
private static var keychain: KeychainSwift { private static var keychain: KeychainSwift {
let keychain = KeychainSwift() let keychain = KeychainSwift()
#if !DEBUG && !targetEnvironment(simulator) #if !DEBUG && !targetEnvironment(simulator)
@ -13,17 +13,17 @@ extension AppAccount {
return keychain return keychain
} }
public func save() throws { func save() throws {
let encoder = JSONEncoder() let encoder = JSONEncoder()
let data = try encoder.encode(self) let data = try encoder.encode(self)
Self.keychain.set(data, forKey: key) Self.keychain.set(data, forKey: key)
} }
public func delete() { func delete() {
Self.keychain.delete(key) Self.keychain.delete(key)
} }
public static func retrieveAll() -> [AppAccount] { static func retrieveAll() -> [AppAccount] {
migrateLegacyAccounts() migrateLegacyAccounts()
let keychain = Self.keychain let keychain = Self.keychain
let decoder = JSONDecoder() let decoder = JSONDecoder()
@ -39,7 +39,7 @@ extension AppAccount {
return accounts return accounts
} }
public static func migrateLegacyAccounts() { static func migrateLegacyAccounts() {
let keychain = KeychainSwift() let keychain = KeychainSwift()
let decoder = JSONDecoder() let decoder = JSONDecoder()
let keys = keychain.allKeys let keys = keychain.allKeys
@ -52,7 +52,7 @@ extension AppAccount {
} }
} }
public static func deleteAll() { static func deleteAll() {
let keychain = Self.keychain let keychain = Self.keychain
let keys = keychain.allKeys let keys = keychain.allKeys
for key in keys { for key in keys {

View file

@ -101,7 +101,7 @@ public struct AppAccountsSelectorView: View {
Label("app-account.button.add", systemImage: "person.badge.plus") Label("app-account.button.add", systemImage: "person.badge.plus")
} }
} }
if UIDevice.current.userInterfaceIdiom == .phone { if UIDevice.current.userInterfaceIdiom == .phone {
Divider() Divider()
Button { Button {

View file

@ -39,8 +39,8 @@ public struct ConversationDetailView: View {
ForEach(viewModel.messages) { message in ForEach(viewModel.messages) { message in
ConversationMessageView(message: message, ConversationMessageView(message: message,
conversation: viewModel.conversation) conversation: viewModel.conversation)
.padding(.vertical, 4) .padding(.vertical, 4)
.id(message.id) .id(message.id)
} }
bottomAnchorView bottomAnchorView
} }
@ -129,7 +129,7 @@ public struct ConversationDetailView: View {
.overlay( .overlay(
RoundedRectangle(cornerRadius: 14) RoundedRectangle(cornerRadius: 14)
.stroke(.gray, lineWidth: 1) .stroke(.gray, lineWidth: 1)
) )
.font(.scaledBody) .font(.scaledBody)
if !viewModel.newMessageText.isEmpty { if !viewModel.newMessageText.isEmpty {
Button { Button {

View file

@ -68,7 +68,7 @@ struct ConversationMessageView: View {
} }
Group { Group {
Text(message.createdAt.shortDateFormatted) + Text(message.createdAt.shortDateFormatted) +
Text(" ") Text(" ")
Text(message.createdAt.asDate, style: .time) Text(message.createdAt.asDate, style: .time)
} }
.font(.scaledFootnote) .font(.scaledFootnote)

View file

@ -9,11 +9,11 @@ public class CurrentInstance: ObservableObject {
private var client: Client? private var client: Client?
public static let shared = CurrentInstance() public static let shared = CurrentInstance()
public var isFiltersSupported: Bool { public var isFiltersSupported: Bool {
instance?.version.hasPrefix("4") == true instance?.version.hasPrefix("4") == true
} }
public var isEditSupported: Bool { public var isEditSupported: Bool {
instance?.version.hasPrefix("4") == true instance?.version.hasPrefix("4") == true
} }

View file

@ -27,7 +27,7 @@ public class PushNotificationsService: ObservableObject {
} }
public static let shared = PushNotificationsService() public static let shared = PushNotificationsService()
public private(set) var subscriptions: [PushNotificationSubscriptionSettings] = [] public private(set) var subscriptions: [PushNotificationSubscriptionSettings] = []
@Published public var pushToken: Data? @Published public var pushToken: Data?
@ -47,7 +47,7 @@ public class PushNotificationsService: ObservableObject {
} }
} }
} }
public func setAccounts(accounts: [PushAccount]) { public func setAccounts(accounts: [PushAccount]) {
subscriptions = [] subscriptions = []
for account in accounts { for account in accounts {
@ -58,13 +58,13 @@ public class PushNotificationsService: ObservableObject {
subscriptions.append(sub) subscriptions.append(sub)
} }
} }
public func updateSubscriptions(forceCreate: Bool) async { public func updateSubscriptions(forceCreate: Bool) async {
for subscription in subscriptions { for subscription in subscriptions {
await withTaskGroup(of: Void.self, body: { group in await withTaskGroup(of: Void.self, body: { group in
group.addTask { group.addTask {
await subscription.fetchSubscription() await subscription.fetchSubscription()
if await subscription.subscription != nil && !forceCreate { if await subscription.subscription != nil, !forceCreate {
await subscription.deleteSubscription() await subscription.deleteSubscription()
await subscription.updateSubscription() await subscription.updateSubscription()
} else if forceCreate { } else if forceCreate {
@ -136,23 +136,23 @@ public class PushNotificationSubscriptionSettings: ObservableObject {
@Published public var isMentionNotificationEnabled: Bool = true @Published public var isMentionNotificationEnabled: Bool = true
@Published public var isPollNotificationEnabled: Bool = true @Published public var isPollNotificationEnabled: Bool = true
@Published public var isNewPostsNotificationEnabled: Bool = true @Published public var isNewPostsNotificationEnabled: Bool = true
public let account: PushAccount public let account: PushAccount
private let key: Data private let key: Data
private let authKey: Data private let authKey: Data
public var pushToken: Data? public var pushToken: Data?
public private(set) var subscription: PushSubscription? public private(set) var subscription: PushSubscription?
public init(account: PushAccount, key: Data, authKey: Data, pushToken: Data?) { public init(account: PushAccount, key: Data, authKey: Data, pushToken: Data?) {
self.account = account self.account = account
self.key = key self.key = key
self.authKey = authKey self.authKey = authKey
self.pushToken = pushToken self.pushToken = pushToken
} }
private func refreshSubscriptionsUI() { private func refreshSubscriptionsUI() {
if let subscription { if let subscription {
isFollowNotificationEnabled = subscription.alerts.follow isFollowNotificationEnabled = subscription.alerts.follow
@ -163,7 +163,7 @@ public class PushNotificationSubscriptionSettings: ObservableObject {
isNewPostsNotificationEnabled = subscription.alerts.status isNewPostsNotificationEnabled = subscription.alerts.status
} }
} }
public func updateSubscription() async { public func updateSubscription() async {
guard let pushToken = pushToken else { return } guard let pushToken = pushToken else { return }
let client = Client(server: account.server, oauthToken: account.token) let client = Client(server: account.server, oauthToken: account.token)
@ -176,23 +176,23 @@ public class PushNotificationSubscriptionSettings: ObservableObject {
listenerURL += "?sandbox=true" listenerURL += "?sandbox=true"
#endif #endif
subscription = subscription =
try await client.post(endpoint: Push.createSub(endpoint: listenerURL, try await client.post(endpoint: Push.createSub(endpoint: listenerURL,
p256dh: key, p256dh: key,
auth: authKey, auth: authKey,
mentions: isMentionNotificationEnabled, mentions: isMentionNotificationEnabled,
status: isNewPostsNotificationEnabled, status: isNewPostsNotificationEnabled,
reblog: isReblogNotificationEnabled, reblog: isReblogNotificationEnabled,
follow: isFollowNotificationEnabled, follow: isFollowNotificationEnabled,
favorite: isFavoriteNotificationEnabled, favorite: isFavoriteNotificationEnabled,
poll: isPollNotificationEnabled)) poll: isPollNotificationEnabled))
isEnabled = subscription != nil isEnabled = subscription != nil
} catch { } catch {
isEnabled = false isEnabled = false
} }
refreshSubscriptionsUI() refreshSubscriptionsUI()
} }
public func deleteSubscription() async { public func deleteSubscription() async {
let client = Client(server: account.server, oauthToken: account.token) let client = Client(server: account.server, oauthToken: account.token)
do { do {
@ -206,7 +206,7 @@ public class PushNotificationSubscriptionSettings: ObservableObject {
isEnabled = false isEnabled = false
} catch {} } catch {}
} }
public func fetchSubscription() async { public func fetchSubscription() async {
let client = Client(server: account.server, oauthToken: account.token) let client = Client(server: account.server, oauthToken: account.token)
do { do {

View file

@ -35,7 +35,7 @@ public enum SheetDestinations: Identifiable {
public var id: String { public var id: String {
switch self { switch self {
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor, case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
.mentionStatusEditor, .settings, .accountPushNotficationsSettings: .mentionStatusEditor, .settings, .accountPushNotficationsSettings:
return "statusEditor" return "statusEditor"
case .listEdit: case .listEdit:
return "listEdit" return "listEdit"
@ -67,8 +67,8 @@ public class RouterPath: ObservableObject {
public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result { public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result {
if url.pathComponents.count == 3 && url.pathComponents[1] == "tags" && if url.pathComponents.count == 3 && url.pathComponents[1] == "tags" &&
url.host() == status.account.url?.host(), url.host() == status.account.url?.host(),
let tag = url.pathComponents.last let tag = url.pathComponents.last
{ {
// OK this test looks weird but it's // OK this test looks weird but it's
// A 3 component path i.e. ["/", "tags", "tagname"] // A 3 component path i.e. ["/", "tags", "tagname"]
@ -110,7 +110,8 @@ public class RouterPath: ObservableObject {
} else if let client = client, } else if let client = client,
client.isAuth, client.isAuth,
client.hasConnection(with: url), client.hasConnection(with: url),
let id = Int(url.lastPathComponent) { let id = Int(url.lastPathComponent)
{
if url.absoluteString.contains(client.server) { if url.absoluteString.contains(client.server) {
navigate(to: .statusDetail(id: String(id))) navigate(to: .statusDetail(id: String(id)))
} else { } else {

View file

@ -19,59 +19,44 @@ public class UserPreferences: ObservableObject {
@AppStorage("recently_used_languages") public var recentlyUsedLanguages: [String] = [] @AppStorage("recently_used_languages") public var recentlyUsedLanguages: [String] = []
@AppStorage("social_keyboard_composer") public var isSocialKeyboardEnabled: Bool = true @AppStorage("social_keyboard_composer") public var isSocialKeyboardEnabled: Bool = true
@AppStorage("use_instance_content_settings") public var useInstanceContentSettings: Bool = true @AppStorage("use_instance_content_settings") public var useInstanceContentSettings: Bool = true
@AppStorage("app_auto_expand_spoilers") public var appAutoExpandSpoilers = false @AppStorage("app_auto_expand_spoilers") public var appAutoExpandSpoilers = false
@AppStorage("app_auto_expand_media") public var appAutoExpandMedia:ServerPreferences.AutoExpandMedia = .hideSensitive @AppStorage("app_auto_expand_media") public var appAutoExpandMedia: ServerPreferences.AutoExpandMedia = .hideSensitive
@AppStorage("app_default_post_visibility") public var appDefaultPostVisibility:Models.Visibility = .pub @AppStorage("app_default_post_visibility") public var appDefaultPostVisibility: Models.Visibility = .pub
@AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false @AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false
public var postVisibility: Models.Visibility {
if useInstanceContentSettings {
return serverPreferences?.postVisibility ?? .pub
} else {
return appDefaultPostVisibility
}
}
public var postVisibility:Models.Visibility { public var postIsSensitive: Bool {
get{ if useInstanceContentSettings {
if useInstanceContentSettings { return serverPreferences?.postIsSensitive ?? false
return serverPreferences?.postVisibility ?? .pub } else {
} return appDefaultPostsSensitive
else {
return appDefaultPostVisibility
}
} }
} }
public var postIsSensitive:Bool {
get {
if useInstanceContentSettings {
return serverPreferences?.postIsSensitive ?? false
}
else {
return appDefaultPostsSensitive
}
}
}
public var autoExpandSpoilers: Bool { public var autoExpandSpoilers: Bool {
get { if useInstanceContentSettings {
if useInstanceContentSettings { return serverPreferences?.autoExpandSpoilers ?? true
return serverPreferences?.autoExpandSpoilers ?? true } else {
} return appAutoExpandSpoilers
else {
return appAutoExpandSpoilers
}
} }
} }
public var autoExpandMedia: ServerPreferences.AutoExpandMedia { public var autoExpandMedia: ServerPreferences.AutoExpandMedia {
get { if useInstanceContentSettings {
if useInstanceContentSettings { return serverPreferences?.autoExpandMedia ?? .hideSensitive
return serverPreferences?.autoExpandMedia ?? .hideSensitive } else {
} return appAutoExpandMedia
else {
return appAutoExpandMedia
}
} }
} }
public var pushNotificationsCount: Int { public var pushNotificationsCount: Int {
get { get {

View file

@ -33,7 +33,7 @@ extension ServerDate {
public var relativeFormatted: String { public var relativeFormatted: String {
return Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date()) return Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date())
} }
public var shortDateFormatted: String { public var shortDateFormatted: String {
return Self.createdAtShortDateFormatted.string(from: asDate) return Self.createdAtShortDateFormatted.string(from: asDate)
} }

View file

@ -5,7 +5,7 @@ public struct AppAccount: Codable, Identifiable, Hashable {
public let server: String public let server: String
public var accountName: String? public var accountName: String?
public let oauthToken: OauthToken? public let oauthToken: OauthToken?
public var key: String { public var key: String {
if let oauthToken { if let oauthToken {
return "\(server):\(oauthToken.createdAt)" return "\(server):\(oauthToken.createdAt)"
@ -13,14 +13,15 @@ public struct AppAccount: Codable, Identifiable, Hashable {
return "\(server):anonymous" return "\(server):anonymous"
} }
} }
public var id: String { public var id: String {
key key
} }
public init(server: String, public init(server: String,
accountName: String?, accountName: String?,
oauthToken: OauthToken? = nil) { oauthToken: OauthToken? = nil)
{
self.server = server self.server = server
self.accountName = accountName self.accountName = accountName
self.oauthToken = oauthToken self.oauthToken = oauthToken

View file

@ -31,13 +31,13 @@ public struct Poll: Codable, Equatable, Hashable {
public struct NullableString: Codable, Equatable, Hashable { public struct NullableString: Codable, Equatable, Hashable {
public let value: String? public let value: String?
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
do { do {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
self.value = try container.decode(String.self) value = try container.decode(String.self)
} catch { } catch {
self.value = nil value = nil
} }
} }
} }

View file

@ -6,15 +6,15 @@ public struct ServerFilter: Codable, Identifiable, Hashable {
public let keyword: String public let keyword: String
public let wholeWord: Bool public let wholeWord: Bool
} }
public enum Context: String, Codable, CaseIterable { public enum Context: String, Codable, CaseIterable {
case home, notifications, `public`, thread, account case home, notifications, `public`, thread, account
} }
public enum Action: String, Codable, CaseIterable { public enum Action: String, Codable, CaseIterable {
case warn, hide case warn, hide
} }
public let id: String public let id: String
public let title: String public let title: String
public let keywords: [Keyword] public let keywords: [Keyword]
@ -23,8 +23,8 @@ public struct ServerFilter: Codable, Identifiable, Hashable {
public let expireIn: Int? public let expireIn: Int?
} }
extension ServerFilter.Context { public extension ServerFilter.Context {
public var iconName: String { var iconName: String {
switch self { switch self {
case .home: case .home:
return "rectangle.on.rectangle" return "rectangle.on.rectangle"
@ -38,8 +38,8 @@ extension ServerFilter.Context {
return "person.crop.circle" return "person.crop.circle"
} }
} }
public var name: String { var name: String {
switch self { switch self {
case .home: case .home:
return "Home and lists" return "Home and lists"
@ -55,8 +55,8 @@ extension ServerFilter.Context {
} }
} }
extension ServerFilter.Action { public extension ServerFilter.Action {
public var label: String { var label: String {
switch self { switch self {
case .warn: case .warn:
return "Hide with a warning" return "Hide with a warning"

View file

@ -12,7 +12,7 @@ public struct ServerPreferences: Decodable {
case showAll = "show_all" case showAll = "show_all"
case hideAll = "hide_all" case hideAll = "hide_all"
case hideSensitive = "default" case hideSensitive = "default"
public var description: LocalizedStringKey { public var description: LocalizedStringKey {
switch self { switch self {
case .showAll: case .showAll:

View file

@ -30,12 +30,12 @@ public enum ServerFilters: Endpoint {
switch self { switch self {
case let .addKeyword(_, keyword, wholeWord): case let .addKeyword(_, keyword, wholeWord):
return [.init(name: "keyword", value: keyword), return [.init(name: "keyword", value: keyword),
.init(name: "whole_word", value: wholeWord ? "true": "false")] .init(name: "whole_word", value: wholeWord ? "true" : "false")]
default: default:
return nil return nil
} }
} }
public var jsonValue: Encodable? { public var jsonValue: Encodable? {
switch self { switch self {
case let .createFilter(json): case let .createFilter(json):
@ -53,11 +53,12 @@ public struct ServerFilterData: Encodable {
public let context: [ServerFilter.Context] public let context: [ServerFilter.Context]
public let filterAction: ServerFilter.Action public let filterAction: ServerFilter.Action
public let expireIn: Int? public let expireIn: Int?
public init(title: String, public init(title: String,
context: [ServerFilter.Context], context: [ServerFilter.Context],
filterAction: ServerFilter.Action, filterAction: ServerFilter.Action,
expireIn: Int?) { expireIn: Int?)
{
self.title = title self.title = title
self.context = context self.context = context
self.filterAction = filterAction self.filterAction = filterAction

View file

@ -76,7 +76,7 @@ class NotificationsViewModel: ObservableObject {
newNotifications = newNotifications.filter { notification in newNotifications = newNotifications.filter { notification in
!consolidatedNotifications.contains(where: { $0.id == notification.id }) !consolidatedNotifications.contains(where: { $0.id == notification.id })
} }
self.notifications.append(contentsOf: newNotifications) notifications.append(contentsOf: newNotifications)
consolidatedNotifications.insert(contentsOf: newNotifications.consolidated(), at: 0) consolidatedNotifications.insert(contentsOf: newNotifications.consolidated(), at: 0)
} }
withAnimation { withAnimation {
@ -98,7 +98,7 @@ class NotificationsViewModel: ObservableObject {
maxId: lastId, maxId: lastId,
types: queryTypes)) types: queryTypes))
consolidatedNotifications.append(contentsOf: newNotifications.consolidated()) consolidatedNotifications.append(contentsOf: newNotifications.consolidated())
self.notifications.append(contentsOf: newNotifications) notifications.append(contentsOf: newNotifications)
state = .display(notifications: consolidatedNotifications, nextPageState: newNotifications.count < 15 ? .none : .hasNextPage) state = .display(notifications: consolidatedNotifications, nextPageState: newNotifications.count < 15 ? .none : .hasNextPage)
} catch { } catch {
state = .error(error: error) state = .error(error: error)

View file

@ -9,7 +9,7 @@ struct StatusEditorMediaView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ObservedObject var viewModel: StatusEditorViewModel @ObservedObject var viewModel: StatusEditorViewModel
@State private var editingContainer: StatusEditorMediaContainer? @State private var editingContainer: StatusEditorMediaContainer?
@State private var isErrorDisplayed: Bool = false @State private var isErrorDisplayed: Bool = false
var body: some View { var body: some View {
@ -142,7 +142,7 @@ struct StatusEditorMediaView: View {
Label("action.view.error", systemImage: "exclamationmark.triangle") Label("action.view.error", systemImage: "exclamationmark.triangle")
} }
} }
Button(role: .destructive) { Button(role: .destructive) {
withAnimation { withAnimation {
viewModel.mediasImages.removeAll(where: { $0.id == container.id }) viewModel.mediasImages.removeAll(where: { $0.id == container.id })
@ -151,7 +151,7 @@ struct StatusEditorMediaView: View {
Label("action.delete", systemImage: "trash") Label("action.delete", systemImage: "trash")
} }
} }
private func makeErrorView(error: ServerError) -> some View { private func makeErrorView(error: ServerError) -> some View {
ZStack { ZStack {
placeholderView placeholderView
@ -159,11 +159,10 @@ struct StatusEditorMediaView: View {
.foregroundColor(.red) .foregroundColor(.red)
} }
.alert("alert.error", isPresented: $isErrorDisplayed) { .alert("alert.error", isPresented: $isErrorDisplayed) {
Button("Ok", action: { }) Button("Ok", action: {})
} message: { } message: {
Text(error.error ?? "") Text(error.error ?? "")
} }
} }
private var altMarker: some View { private var altMarker: some View {

View file

@ -1,9 +1,9 @@
import AVFoundation
import Foundation import Foundation
import PhotosUI import PhotosUI
import SwiftUI import SwiftUI
import UIKit import UIKit
import UniformTypeIdentifiers import UniformTypeIdentifiers
import AVFoundation
@MainActor @MainActor
enum StatusEditorUTTypeSupported: String, CaseIterable { enum StatusEditorUTTypeSupported: String, CaseIterable {
@ -74,7 +74,7 @@ struct MovieFileTranseferable: Transferable {
private let url: URL private let url: URL
var compressedVideoURL: URL? { var compressedVideoURL: URL? {
get async { get async {
return await withCheckedContinuation { continuation in await withCheckedContinuation { continuation in
let urlAsset = AVURLAsset(url: url, options: nil) let urlAsset = AVURLAsset(url: url, options: nil)
guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else { guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else {
continuation.resume(returning: nil) continuation.resume(returning: nil)
@ -84,7 +84,7 @@ struct MovieFileTranseferable: Transferable {
exportSession.outputURL = outputURL exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4 exportSession.outputFileType = .mp4
exportSession.shouldOptimizeForNetworkUse = true exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously { () -> Void in exportSession.exportAsynchronously { () in
continuation.resume(returning: outputURL) continuation.resume(returning: outputURL)
} }
} }

View file

@ -56,6 +56,7 @@ public class StatusEditorViewModel: ObservableObject {
inflateSelectedMedias() inflateSelectedMedias()
} }
} }
@Published var isMediasLoading: Bool = false @Published var isMediasLoading: Bool = false
@Published var mediasImages: [StatusEditorMediaContainer] = [] @Published var mediasImages: [StatusEditorMediaContainer] = []

View file

@ -22,7 +22,7 @@ public struct StatusRowView: View {
var contextMenu: some View { var contextMenu: some View {
StatusRowContextMenu(viewModel: viewModel) StatusRowContextMenu(viewModel: viewModel)
} }
public var body: some View { public var body: some View {
if viewModel.isFiltered, let filter = viewModel.filter { if viewModel.isFiltered, let filter = viewModel.filter {
switch filter.filter.filterAction { switch filter.filter.filterAction {
@ -225,13 +225,12 @@ public struct StatusRowView: View {
private func makeStatusContentView(status: AnyStatus) -> some View { private func makeStatusContentView(status: AnyStatus) -> some View {
Group { Group {
if !status.spoilerText.asRawText.isEmpty { if !status.spoilerText.asRawText.isEmpty {
HStack(alignment: .top) { HStack(alignment: .top) {
Text("⚠︎") Text("⚠︎")
.font(.system(.subheadline , weight:.bold)) .font(.system(.subheadline, weight: .bold))
.foregroundColor(.secondary) .foregroundColor(.secondary)
EmojiTextApp(status.spoilerText, emojis: status.emojis, language: status.language) EmojiTextApp(status.spoilerText, emojis: status.emojis, language: status.language)
.font(.system(.subheadline , weight:.bold)) .font(.system(.subheadline, weight: .bold))
.foregroundColor(.secondary) .foregroundColor(.secondary)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
Spacer() Spacer()
@ -247,7 +246,7 @@ public struct StatusRowView: View {
.accessibility(label: viewModel.displaySpoiler ? Text("status.show-more") : Text("status.show-less")) .accessibility(label: viewModel.displaySpoiler ? Text("status.show-more") : Text("status.show-less"))
.accessibilityHidden(true) .accessibilityHidden(true)
} }
.onTapGesture { // make whole row tapable to make up for smaller button size .onTapGesture { // make whole row tapable to make up for smaller button size
withAnimation { withAnimation {
viewModel.displaySpoiler.toggle() viewModel.displaySpoiler.toggle()
} }
@ -376,7 +375,8 @@ public struct StatusRowView: View {
!viewModel.isCompact, !viewModel.isCompact,
theme.statusDisplayStyle == .large, theme.statusDisplayStyle == .large,
status.content.statusesURLs.isEmpty, status.content.statusesURLs.isEmpty,
status.mediaAttachments.isEmpty { status.mediaAttachments.isEmpty
{
StatusCardView(card: card) StatusCardView(card: card)
} }
} }