SwiftFormat

This commit is contained in:
Thomas Ricouard 2023-03-13 13:38:28 +01:00
parent f1267620be
commit 6c307aba63
58 changed files with 313 additions and 299 deletions

View file

@ -3,13 +3,13 @@ import AppAccount
import Conversations import Conversations
import DesignSystem import DesignSystem
import Env import Env
import Explore
import LinkPresentation import LinkPresentation
import Lists import Lists
import Models import Models
import Status import Status
import SwiftUI import SwiftUI
import Timeline import Timeline
import Explore
@MainActor @MainActor
extension View { extension View {

View file

@ -112,7 +112,8 @@ struct IceCubesApp: App {
SideBarView(selectedTab: $selectedTab, SideBarView(selectedTab: $selectedTab,
popToRootTab: $popToRootTab, popToRootTab: $popToRootTab,
tabs: availableTabs, tabs: availableTabs,
routerPath: sidebarRouterPath) { routerPath: sidebarRouterPath)
{
GeometryReader { _ in GeometryReader { _ in
HStack(spacing: 0) { HStack(spacing: 0) {
ZStack { ZStack {

View file

@ -30,17 +30,15 @@ struct AddAccountView: View {
private let instanceNamePublisher = PassthroughSubject<String, Never>() private let instanceNamePublisher = PassthroughSubject<String, Never>()
private var sanitizedName: String { private var sanitizedName: String {
get { var name = instanceName
var name = instanceName .replacingOccurrences(of: "http://", with: "")
.replacingOccurrences(of: "http://", with: "") .replacingOccurrences(of: "https://", with: "")
.replacingOccurrences(of: "https://", with: "")
if name.contains("@") { if name.contains("@") {
let parts = name.components(separatedBy: "@") let parts = name.components(separatedBy: "@")
name = parts[parts.count-1] // [@]username@server.address.com name = parts[parts.count - 1] // [@]username@server.address.com
}
return name
} }
return name
} }
@FocusState private var isInstanceURLFieldFocused: Bool @FocusState private var isInstanceURLFieldFocused: Bool
@ -94,8 +92,8 @@ struct AddAccountView: View {
.onChange(of: instanceName) { newValue in .onChange(of: instanceName) { newValue in
instanceNamePublisher.send(newValue) instanceNamePublisher.send(newValue)
} }
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in .onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { _ in
//let newValue = newValue // let newValue = newValue
// .replacingOccurrences(of: "http://", with: "") // .replacingOccurrences(of: "http://", with: "")
// .replacingOccurrences(of: "https://", with: "") // .replacingOccurrences(of: "https://", with: "")
let client = Client(server: sanitizedName) let client = Client(server: sanitizedName)
@ -106,7 +104,7 @@ struct AddAccountView: View {
let instance: Instance = try await client.get(endpoint: Instances.instance) let instance: Instance = try await client.get(endpoint: Instances.instance)
withAnimation { withAnimation {
self.instance = instance self.instance = instance
self.instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box self.instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
} }
instanceFetchError = nil instanceFetchError = nil
} else { } else {

View file

@ -1,10 +1,10 @@
import Combine
import DesignSystem import DesignSystem
import Env import Env
import Models import Models
import Network import Network
import Status import Status
import SwiftUI import SwiftUI
import Combine
class DisplaySettingsLocalColors: ObservableObject { class DisplaySettingsLocalColors: ObservableObject {
@Published var tintColor = Theme.shared.tintColor @Published var tintColor = Theme.shared.tintColor
@ -17,19 +17,19 @@ class DisplaySettingsLocalColors: ObservableObject {
init() { init() {
$tintColor $tintColor
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.sink(receiveValue: { newColor in Theme.shared.tintColor = newColor } ) .sink(receiveValue: { newColor in Theme.shared.tintColor = newColor })
.store(in: &subscriptions) .store(in: &subscriptions)
$primaryBackgroundColor $primaryBackgroundColor
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.sink(receiveValue: { newColor in Theme.shared.primaryBackgroundColor = newColor } ) .sink(receiveValue: { newColor in Theme.shared.primaryBackgroundColor = newColor })
.store(in: &subscriptions) .store(in: &subscriptions)
$secondaryBackgroundColor $secondaryBackgroundColor
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.sink(receiveValue: { newColor in Theme.shared.secondaryBackgroundColor = newColor } ) .sink(receiveValue: { newColor in Theme.shared.secondaryBackgroundColor = newColor })
.store(in: &subscriptions) .store(in: &subscriptions)
$labelColor $labelColor
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.sink(receiveValue: { newColor in Theme.shared.labelColor = newColor } ) .sink(receiveValue: { newColor in Theme.shared.labelColor = newColor })
.store(in: &subscriptions) .store(in: &subscriptions)
} }
} }

View file

@ -105,7 +105,7 @@ struct SupportAppView: View {
private func fetchStoreProducts() { private func fetchStoreProducts() {
Purchases.shared.getProducts(Tip.allCases.map { $0.productId }) { products in Purchases.shared.getProducts(Tip.allCases.map { $0.productId }) { products in
self.subscription = products.first(where: { $0.productIdentifier == Tip.supporter.productId }) self.subscription = products.first(where: { $0.productIdentifier == Tip.supporter.productId })
self.products = products.filter{ $0.productIdentifier != Tip.supporter.productId}.sorted(by: { $0.price < $1.price }) self.products = products.filter { $0.productIdentifier != Tip.supporter.productId }.sorted(by: { $0.price < $1.price })
withAnimation { withAnimation {
loadingProducts = false loadingProducts = false
} }
@ -163,14 +163,14 @@ struct SupportAppView: View {
Text(Image(systemName: "checkmark.seal.fill")) Text(Image(systemName: "checkmark.seal.fill"))
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
.baselineOffset(-1) + .baselineOffset(-1) +
Text("settings.support.supporter.subscribed") Text("settings.support.supporter.subscribed")
.font(.scaledSubheadline) .font(.scaledSubheadline)
} else { } else {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(Image(systemName: "checkmark.seal.fill")) Text(Image(systemName: "checkmark.seal.fill"))
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
.baselineOffset(-1) + .baselineOffset(-1) +
Text(Tip.supporter.title) Text(Tip.supporter.title)
.font(.scaledSubheadline) .font(.scaledSubheadline)
Text(Tip.supporter.subtitle) Text(Tip.supporter.subtitle)
.font(.scaledFootnote) .font(.scaledFootnote)
@ -179,7 +179,6 @@ struct SupportAppView: View {
Spacer() Spacer()
makePurchaseButton(product: subscription) makePurchaseButton(product: subscription)
} }
} }
.padding(.vertical, 8) .padding(.vertical, 8)
} }

View file

@ -1,9 +1,9 @@
import Account import Account
import DesignSystem
import Explore import Explore
import Foundation import Foundation
import Status import Status
import SwiftUI import SwiftUI
import DesignSystem
enum Tab: Int, Identifiable, Hashable { enum Tab: Int, Identifiable, Hashable {
case timeline, notifications, mentions, explore, messages, settings, other case timeline, notifications, mentions, explore, messages, settings, other

View file

@ -54,7 +54,8 @@ class ShareViewController: UIViewController {
NotificationCenter.default.addObserver(forName: NotificationsName.shareSheetClose, NotificationCenter.default.addObserver(forName: NotificationsName.shareSheetClose,
object: nil, object: nil,
queue: nil) { _ in queue: nil)
{ _ in
self.close() self.close()
} }
} }

View file

@ -1,6 +1,6 @@
import SwiftUI
import Env import Env
import Network import Network
import SwiftUI
public struct AccountDetailContextMenu: View { public struct AccountDetailContextMenu: View {
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client

View file

@ -51,7 +51,8 @@ public struct AccountDetailView: View {
Picker("", selection: $viewModel.selectedTab) { Picker("", selection: $viewModel.selectedTab) {
ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs, ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs,
id: \.self) { tab in id: \.self)
{ tab in
Image(systemName: tab.iconName) Image(systemName: tab.iconName)
.tag(tab) .tag(tab)
} }

View file

@ -137,9 +137,9 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
featuredTags: featuredTags, featuredTags: featuredTags,
relationships: relationships) relationships: relationships)
} catch { } catch {
return try await .init(account: account, return try await .init(account: account,
featuredTags: [], featuredTags: [],
relationships: relationships) relationships: relationships)
} }
} }
return try await .init(account: account, return try await .init(account: account,

View file

@ -89,7 +89,7 @@ public struct AccountsListRow: View {
AccountDetailHeaderView(viewModel: .init(account: viewModel.account), AccountDetailHeaderView(viewModel: .init(account: viewModel.account),
account: viewModel.account, account: viewModel.account,
scrollViewProxy: nil) scrollViewProxy: nil)
.applyAccountDetailsRowStyle(theme: theme) .applyAccountDetailsRowStyle(theme: theme)
} }
.listStyle(.plain) .listStyle(.plain)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
@ -98,6 +98,5 @@ public struct AccountsListRow: View {
.environmentObject(currentAccount) .environmentObject(currentAccount)
.environmentObject(client) .environmentObject(client)
} }
} }
} }

View file

@ -23,9 +23,9 @@ struct EditFilterView: View {
@FocusState private var isTitleFocused: Bool @FocusState private var isTitleFocused: Bool
private var data: ServerFilterData { private var data: ServerFilterData {
var expiresIn: String? = nil; var expiresIn: String?
// we add 50 seconds, otherwise we immediately show 6d for a 7d filter (6d, 23h, 59s) // we add 50 seconds, otherwise we immediately show 6d for a 7d filter (6d, 23h, 59s)
switch(expirySelection){ switch expirySelection {
case .infinite: case .infinite:
expiresIn = "" // need to send an empty value in order for the server to clear this field in the filter expiresIn = "" // need to send an empty value in order for the server to clear this field in the filter
case .custom: case .custom:
@ -35,9 +35,9 @@ struct EditFilterView: View {
} }
return ServerFilterData(title: title, return ServerFilterData(title: title,
context: contexts, context: contexts,
filterAction: filterAction, filterAction: filterAction,
expiresIn: expiresIn) expiresIn: expiresIn)
} }
private var canSave: Bool { private var canSave: Bool {
@ -95,10 +95,9 @@ struct EditFilterView: View {
} }
if expirySelection != .infinite { if expirySelection != .infinite {
DatePicker("filter.edit.expiry.date-time", DatePicker("filter.edit.expiry.date-time",
selection: Binding<Date>(get: {self.expiresAt ?? Date()}, set: {self.expiresAt = $0}), selection: Binding<Date>(get: { self.expiresAt ?? Date() }, set: { self.expiresAt = $0 }),
displayedComponents: [.date, .hourAndMinute] displayedComponents: [.date, .hourAndMinute])
) .disabled(expirySelection != .custom)
.disabled(expirySelection != .custom)
} }
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)

View file

@ -51,7 +51,8 @@ public struct AppAccountView: View {
.offset(x: 5, y: -5) .offset(x: 5, y: -5)
} else if viewModel.showBadge, } else if viewModel.showBadge,
let token = viewModel.appAccount.oauthToken, let token = viewModel.appAccount.oauthToken,
preferences.getNotificationsCount(for: token) > 0 { preferences.getNotificationsCount(for: token) > 0
{
let notificationsCount = preferences.getNotificationsCount(for: token) let notificationsCount = preferences.getNotificationsCount(for: token)
ZStack { ZStack {
Circle() Circle()

View file

@ -48,9 +48,9 @@ public struct AppAccountsSelectorView: View {
} }
.sheet(isPresented: $isPresented, content: { .sheet(isPresented: $isPresented, content: {
accountsView.presentationDetents([.height(preferredHeight), .large]) accountsView.presentationDetents([.height(preferredHeight), .large])
.onAppear { .onAppear {
refreshAccounts() refreshAccounts()
} }
}) })
.onChange(of: currentAccount.account?.id) { _ in .onChange(of: currentAccount.account?.id) { _ in
refreshAccounts() refreshAccounts()

View file

@ -177,7 +177,8 @@ struct ConversationMessageView: View {
let width = mediaWidth(proxy: proxy) let width = mediaWidth(proxy: proxy)
if let url = attachement.url { if let url = attachement.url {
LazyImage(request: makeImageRequest(for: url, LazyImage(request: makeImageRequest(for: url,
size: .init(width: width, height: 200))) { state in size: .init(width: width, height: 200)))
{ state in
if let image = state.image { if let image = state.image {
image image
.resizable() .resizable()

View file

@ -47,7 +47,8 @@ public struct ConversationsListView: View {
} else if viewModel.isError { } else if viewModel.isError {
ErrorView(title: "conversations.error.title", ErrorView(title: "conversations.error.title",
message: "conversations.error.message", message: "conversations.error.message",
buttonTitle: "conversations.error.button") { buttonTitle: "conversations.error.button")
{
Task { Task {
await viewModel.fetchConversations() await viewModel.fetchConversations()
} }

View file

@ -109,11 +109,13 @@ public extension UIFont {
} }
return UIFont(descriptor: descriptor, size: pointSize) return UIFont(descriptor: descriptor, size: pointSize)
} }
var emojiSize: CGFloat { var emojiSize: CGFloat {
self.pointSize pointSize
} }
var emojiBaselineOffset: CGFloat { var emojiBaselineOffset: CGFloat {
// Center emoji with capital letter size of font // Center emoji with capital letter size of font
-(self.emojiSize - self.capHeight) / 2 -(emojiSize - capHeight) / 2
} }
} }

View file

@ -7,15 +7,12 @@ import SwiftUI
// images named in lower case are Apple's symbols // images named in lower case are Apple's symbols
// images inamed in CamelCase are custom // images inamed in CamelCase are custom
extension Label where Title == Text, Icon == Image { public extension Label where Title == Text, Icon == Image {
init(_ title: LocalizedStringKey, imageNamed: String) {
public init (_ title: LocalizedStringKey, imageNamed: String) {
if imageNamed.lowercased() == imageNamed { if imageNamed.lowercased() == imageNamed {
self.init(title, systemImage: imageNamed) self.init(title, systemImage: imageNamed)
} } else {
else {
self.init(title, image: imageNamed) self.init(title, image: imageNamed)
} }
} }
} }

View file

@ -70,7 +70,6 @@ public class RouterPath: ObservableObject {
@Published public var path: [RouterDestination] = [] @Published public var path: [RouterDestination] = []
@Published public var presentedSheet: SheetDestination? @Published public var presentedSheet: SheetDestination?
public init() {} public init() {}
public func navigate(to: RouterDestination) { public func navigate(to: RouterDestination) {

View file

@ -1,6 +1,6 @@
import AVKit
import CoreHaptics import CoreHaptics
import UIKit import UIKit
import AVKit
public class SoundEffectManager { public class SoundEffectManager {
public static let shared: SoundEffectManager = .init() public static let shared: SoundEffectManager = .init()

View file

@ -1,7 +1,7 @@
import Foundation import Foundation
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
@MainActor @MainActor
public protocol StatusDataControlling: ObservableObject { public protocol StatusDataControlling: ObservableObject {
@ -65,23 +65,23 @@ public final class StatusDataController: StatusDataControlling {
self.status = status self.status = status
self.client = client self.client = client
self.isReblogged = status.reblogged == true isReblogged = status.reblogged == true
self.isBookmarked = status.bookmarked == true isBookmarked = status.bookmarked == true
self.isFavorited = status.favourited == true isFavorited = status.favourited == true
self.reblogsCount = status.reblogsCount reblogsCount = status.reblogsCount
self.repliesCount = status.repliesCount repliesCount = status.repliesCount
self.favoritesCount = status.favouritesCount favoritesCount = status.favouritesCount
} }
public func updateFrom(status: AnyStatus, publishUpdate: Bool) { public func updateFrom(status: AnyStatus, publishUpdate: Bool) {
self.isReblogged = status.reblogged == true isReblogged = status.reblogged == true
self.isBookmarked = status.bookmarked == true isBookmarked = status.bookmarked == true
self.isFavorited = status.favourited == true isFavorited = status.favourited == true
self.reblogsCount = status.reblogsCount reblogsCount = status.reblogsCount
self.repliesCount = status.repliesCount repliesCount = status.repliesCount
self.favoritesCount = status.favouritesCount favoritesCount = status.favouritesCount
if publishUpdate { if publishUpdate {
objectWillChange.send() objectWillChange.send()
@ -105,7 +105,6 @@ public final class StatusDataController: StatusDataControlling {
} }
} }
public func toggleReblog(remoteStatus: String?) async { public func toggleReblog(remoteStatus: String?) async {
guard client.isAuth else { return } guard client.isAuth else { return }
isReblogged.toggle() isReblogged.toggle()

View file

@ -70,7 +70,7 @@ public class UserPreferences: ObservableObject {
// Main actor-isolated static property 'allCases' cannot be used to // Main actor-isolated static property 'allCases' cannot be used to
// satisfy nonisolated protocol requirement // satisfy nonisolated protocol requirement
// //
nonisolated public static var allCases: [Self] { public nonisolated static var allCases: [Self] {
[.iconWithText, .iconOnly] [.iconWithText, .iconOnly]
} }
} }

View file

@ -127,12 +127,13 @@ public struct ExploreView: View {
private var suggestedAccountsSection: some View { private var suggestedAccountsSection: some View {
Section("explore.section.suggested-users") { Section("explore.section.suggested-users") {
ForEach(viewModel.suggestedAccounts ForEach(viewModel.suggestedAccounts
.prefix(upTo: viewModel.suggestedAccounts.count > 3 ? 3 : viewModel.suggestedAccounts.count)) { account in .prefix(upTo: viewModel.suggestedAccounts.count > 3 ? 3 : viewModel.suggestedAccounts.count))
if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) { { account in
AccountsListRow(viewModel: .init(account: account, relationShip: relationship)) if let relationship = viewModel.suggestedAccountsRelationShips.first(where: { $0.id == account.id }) {
.listRowBackground(theme.primaryBackgroundColor) AccountsListRow(viewModel: .init(account: account, relationShip: relationship))
} .listRowBackground(theme.primaryBackgroundColor)
} }
}
NavigationLink(value: RouterDestination.accountsList(accounts: viewModel.suggestedAccounts)) { NavigationLink(value: RouterDestination.accountsList(accounts: viewModel.suggestedAccounts)) {
Text("see-more") Text("see-more")
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
@ -144,11 +145,12 @@ public struct ExploreView: View {
private var trendingTagsSection: some View { private var trendingTagsSection: some View {
Section("explore.section.trending.tags") { Section("explore.section.trending.tags") {
ForEach(viewModel.trendingTags ForEach(viewModel.trendingTags
.prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count)) { tag in .prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count))
TagRowView(tag: tag) { tag in
.listRowBackground(theme.primaryBackgroundColor) TagRowView(tag: tag)
.padding(.vertical, 4) .listRowBackground(theme.primaryBackgroundColor)
} .padding(.vertical, 4)
}
NavigationLink(value: RouterDestination.tagsList(tags: viewModel.trendingTags)) { NavigationLink(value: RouterDestination.tagsList(tags: viewModel.trendingTags)) {
Text("see-more") Text("see-more")
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
@ -160,11 +162,12 @@ public struct ExploreView: View {
private var trendingPostsSection: some View { private var trendingPostsSection: some View {
Section("explore.section.trending.posts") { Section("explore.section.trending.posts") {
ForEach(viewModel.trendingStatuses ForEach(viewModel.trendingStatuses
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in .prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count))
StatusRowView(viewModel: { .init(status: status, client: client, routerPath: routerPath) }) { status in
.listRowBackground(theme.primaryBackgroundColor) StatusRowView(viewModel: { .init(status: status, client: client, routerPath: routerPath) })
.padding(.vertical, 8) .listRowBackground(theme.primaryBackgroundColor)
} .padding(.vertical, 8)
}
NavigationLink(value: RouterDestination.trendingTimeline) { NavigationLink(value: RouterDestination.trendingTimeline) {
Text("see-more") Text("see-more")
@ -177,11 +180,12 @@ public struct ExploreView: View {
private var trendingLinksSection: some View { private var trendingLinksSection: some View {
Section("explore.section.trending.links") { Section("explore.section.trending.links") {
ForEach(viewModel.trendingLinks ForEach(viewModel.trendingLinks
.prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count)) { card in .prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count))
StatusRowCardView(card: card) { card in
.listRowBackground(theme.primaryBackgroundColor) StatusRowCardView(card: card)
.padding(.vertical, 8) .listRowBackground(theme.primaryBackgroundColor)
} .padding(.vertical, 8)
}
NavigationLink { NavigationLink {
List { List {
ForEach(viewModel.trendingLinks) { card in ForEach(viewModel.trendingLinks) { card in

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models
import DesignSystem import DesignSystem
import Models
import SwiftUI
public struct TagsListView: View { public struct TagsListView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme

View file

@ -2,6 +2,7 @@ import Foundation
public struct ServerError: Decodable, Error { public struct ServerError: Decodable, Error {
public let error: String? public let error: String?
public var httpCode: Int
} }
extension ServerError: Sendable {} extension ServerError: Sendable {}

View file

@ -29,7 +29,7 @@ public struct ServerFilter: Codable, Identifiable, Hashable, Sendable {
public func isExpired() -> Bool { public func isExpired() -> Bool {
if let expiresAtDate = expiresAt?.asDate { if let expiresAtDate = expiresAt?.asDate {
return expiresAtDate < Date() return expiresAtDate < Date()
} else { } else {
return false return false
} }

View file

@ -103,7 +103,6 @@ public final class Status: AnyStatus, Codable, Identifiable, Equatable, Hashable
public let sensitive: Bool public let sensitive: Bool
public let language: String? public let language: String?
public init(id: String, content: HTMLString, account: Account, createdAt: ServerDate, editedAt: ServerDate?, reblog: ReblogStatus?, mediaAttachments: [MediaAttachment], mentions: [Mention], repliesCount: Int, reblogsCount: Int, favouritesCount: Int, card: Card?, favourited: Bool?, reblogged: Bool?, pinned: Bool?, bookmarked: Bool?, emojis: [Emoji], url: String?, application: Application?, inReplyToId: String?, inReplyToAccountId: String?, visibility: Visibility, poll: Poll?, spoilerText: HTMLString, filtered: [Filtered]?, sensitive: Bool, language: String?) { public init(id: String, content: HTMLString, account: Account, createdAt: ServerDate, editedAt: ServerDate?, reblog: ReblogStatus?, mediaAttachments: [MediaAttachment], mentions: [Mention], repliesCount: Int, reblogsCount: Int, favouritesCount: Int, card: Card?, favourited: Bool?, reblogged: Bool?, pinned: Bool?, bookmarked: Bool?, emojis: [Emoji], url: String?, application: Application?, inReplyToId: String?, inReplyToAccountId: String?, visibility: Visibility, poll: Poll?, spoilerText: HTMLString, filtered: [Filtered]?, sensitive: Bool, language: String?) {
self.id = id self.id = id
self.content = content self.content = content
@ -277,5 +276,3 @@ extension Status: Sendable {}
// Every property in ReblogStatus is immutable. // Every property in ReblogStatus is immutable.
extension ReblogStatus: Sendable {} extension ReblogStatus: Sendable {}

View file

@ -1,8 +1,8 @@
import Combine import Combine
import Foundation import Foundation
import Models import Models
import SwiftUI
import os import os
import SwiftUI
public final class Client: ObservableObject, Equatable, Identifiable, Hashable { public final class Client: ObservableObject, Equatable, Identifiable, Hashable {
public static func == (lhs: Client, rhs: Client) -> Bool { public static func == (lhs: Client, rhs: Client) -> Bool {
@ -61,7 +61,7 @@ public final class Client: ObservableObject, Equatable, Identifiable, Hashable {
public init(server: String, version: Version = .v1, oauthToken: OauthToken? = nil) { public init(server: String, version: Version = .v1, oauthToken: OauthToken? = nil) {
self.server = server self.server = server
self.version = version self.version = version
self.critical = .init(initialState: Critical(oauthToken: oauthToken, connections: [server])) critical = .init(initialState: Critical(oauthToken: oauthToken, connections: [server]))
urlSession = URLSession.shared urlSession = URLSession.shared
decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.keyDecodingStrategy = .convertFromSnakeCase
} }
@ -141,7 +141,7 @@ public final class Client: ObservableObject, Equatable, Identifiable, Hashable {
linkHandler = .init(rawLink: link) linkHandler = .init(rawLink: link)
} }
logResponseOnError(httpResponse: httpResponse, data: data) logResponseOnError(httpResponse: httpResponse, data: data)
return (try decoder.decode(Entity.self, from: data), linkHandler) return try (decoder.decode(Entity.self, from: data), linkHandler)
} }
public func post<Entity: Decodable>(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> Entity { public func post<Entity: Decodable>(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> Entity {
@ -184,7 +184,10 @@ public final class Client: ObservableObject, Equatable, Identifiable, Hashable {
do { do {
return try decoder.decode(Entity.self, from: data) return try decoder.decode(Entity.self, from: data)
} catch { } catch {
if let serverError = try? decoder.decode(ServerError.self, from: data) { if var serverError = try? decoder.decode(ServerError.self, from: data) {
if let httpResponse = httpResponse as? HTTPURLResponse {
serverError.httpCode = httpResponse.statusCode
}
throw serverError throw serverError
} }
throw error throw error

View file

@ -47,7 +47,7 @@ public struct OpenAIClient {
} }
public init(content: String, temperature: CGFloat) { public init(content: String, temperature: CGFloat) {
self.messages = [.init(content: content)] messages = [.init(content: content)]
self.temperature = temperature self.temperature = temperature
} }
} }

View file

@ -47,23 +47,23 @@ extension Models.Notification.NotificationType {
func icon(isPrivate: Bool) -> Image { func icon(isPrivate: Bool) -> Image {
if isPrivate { if isPrivate {
return Image(systemName:"tray.fill") return Image(systemName: "tray.fill")
} }
switch self { switch self {
case .status: case .status:
return Image(systemName:"pencil") return Image(systemName: "pencil")
case .mention: case .mention:
return Image(systemName:"at") return Image(systemName: "at")
case .reblog: case .reblog:
return Image("Rocket.Fill") return Image("Rocket.Fill")
case .follow, .follow_request: case .follow, .follow_request:
return Image(systemName:"person.fill.badge.plus") return Image(systemName: "person.fill.badge.plus")
case .favourite: case .favourite:
return Image(systemName:"star.fill") return Image(systemName: "star.fill")
case .poll: case .poll:
return Image(systemName:"chart.bar.fill") return Image(systemName: "chart.bar.fill")
case .update: case .update:
return Image(systemName:"pencil.line") return Image(systemName: "pencil.line")
} }
} }

View file

@ -145,7 +145,8 @@ public struct NotificationsListView: View {
case .error: case .error:
ErrorView(title: "notifications.error.title", ErrorView(title: "notifications.error.title",
message: "notifications.error.message", message: "notifications.error.message",
buttonTitle: "action.retry") { buttonTitle: "action.retry")
{
Task { Task {
await viewModel.fetchNotifications() await viewModel.fetchNotifications()
} }

View file

@ -155,7 +155,8 @@ public struct StatusDetailView: View {
private var errorView: some View { private var errorView: some View {
ErrorView(title: "status.error.title", ErrorView(title: "status.error.title",
message: "status.error.message", message: "status.error.message",
buttonTitle: "action.retry") { buttonTitle: "action.retry")
{
Task { Task {
await viewModel.fetch() await viewModel.fetch()
} }

View file

@ -1,8 +1,8 @@
import Env
import Foundation import Foundation
import Models import Models
import Network import Network
import SwiftUI import SwiftUI
import Env
@MainActor @MainActor
class StatusDetailViewModel: ObservableObject { class StatusDetailViewModel: ObservableObject {

View file

@ -51,7 +51,8 @@ struct StatusEditorAccessoryView: View {
matching: .any(of: [.images, .videos])) matching: .any(of: [.images, .videos]))
.fileImporter(isPresented: $isFileImporterPresented, .fileImporter(isPresented: $isFileImporterPresented,
allowedContentTypes: [.image, .video], allowedContentTypes: [.image, .video],
allowsMultipleSelection: true) { result in allowsMultipleSelection: true)
{ result in
if let urls = try? result.get() { if let urls = try? result.get() {
viewModel.processURLs(urls: urls) viewModel.processURLs(urls: urls)
} }
@ -59,7 +60,6 @@ struct StatusEditorAccessoryView: View {
.accessibilityLabel("accessibility.editor.button.attach-photo") .accessibilityLabel("accessibility.editor.button.attach-photo")
.disabled(viewModel.showPoll) .disabled(viewModel.showPoll)
Button { Button {
withAnimation { withAnimation {
viewModel.showPoll.toggle() viewModel.showPoll.toggle()

View file

@ -1,6 +1,6 @@
import AVFoundation
import Foundation import Foundation
import UIKit import UIKit
import AVFoundation
actor StatusEditorCompressor { actor StatusEditorCompressor {
enum CompressorError: Error { enum CompressorError: Error {
@ -8,7 +8,7 @@ actor StatusEditorCompressor {
} }
func compressImageFrom(url: URL) async -> Data? { func compressImageFrom(url: URL) async -> Data? {
return await withCheckedContinuation{ continuation in return await withCheckedContinuation { continuation in
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {
continuation.resume(returning: nil) continuation.resume(returning: nil)
@ -26,7 +26,7 @@ actor StatusEditorCompressor {
kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxPixelSize, kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
] as [CFString : Any] as CFDictionary ] as [CFString: Any] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else {
continuation.resume(returning: nil) continuation.resume(returning: nil)
@ -45,7 +45,7 @@ actor StatusEditorCompressor {
}() }()
let destinationProperties = [ let destinationProperties = [
kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75 kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75,
] as CFDictionary ] as CFDictionary
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties) CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
@ -66,12 +66,13 @@ actor StatusEditorCompressor {
throw CompressorError.noData throw CompressorError.noData
} }
let maxSize: Int = 10 * 1024 * 1024 let maxSize = 10 * 1024 * 1024
if imageData.count > maxSize { if imageData.count > maxSize {
while imageData.count > maxSize { while imageData.count > maxSize {
guard let compressedImage = UIImage(data: imageData), guard let compressedImage = UIImage(data: imageData),
let compressedData = compressedImage.jpegData(compressionQuality: 0.8) else { let compressedData = compressedImage.jpegData(compressionQuality: 0.8)
else {
throw CompressorError.noData throw CompressorError.noData
} }
imageData = compressedData imageData = compressedData
@ -97,5 +98,4 @@ actor StatusEditorCompressor {
} }
} }
} }
} }

View file

@ -31,7 +31,7 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
// Main actor-isolated static property 'allCases' cannot be used to // Main actor-isolated static property 'allCases' cannot be used to
// satisfy nonisolated protocol requirement // satisfy nonisolated protocol requirement
// //
nonisolated public static var allCases: [StatusEditorUTTypeSupported] { public nonisolated static var allCases: [StatusEditorUTTypeSupported] {
[.url, .text, .plaintext, .image, .jpeg, .png, .tiff, .video, [.url, .text, .plaintext, .image, .jpeg, .png, .tiff, .video,
.movie, .mp4, .gif, .gif2, .quickTimeMovie, .uiimage, .adobeRawImage] .movie, .mp4, .gif, .gif2, .quickTimeMovie, .uiimage, .adobeRawImage]
} }

View file

@ -170,7 +170,7 @@ public struct StatusEditorView: View {
@ViewBuilder @ViewBuilder
private var languageConfirmationDialog: some View { private var languageConfirmationDialog: some View {
if let (detected: detected, selected: selected) = viewModel.languageConfirmationDialogLanguages, if let (detected: detected, selected: selected) = viewModel.languageConfirmationDialogLanguages,
let detectedLong = Locale.current.localizedString(forLanguageCode: detected), let detectedLong = Locale.current.localizedString(forLanguageCode: detected),
let selectedLong = Locale.current.localizedString(forLanguageCode: selected) let selectedLong = Locale.current.localizedString(forLanguageCode: selected)
{ {

View file

@ -20,6 +20,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
} }
} }
} }
var theme: Theme? var theme: Theme?
var preferences: UserPreferences? var preferences: UserPreferences?
var languageConfirmationDialogLanguages: (detected: String, selected: String)? var languageConfirmationDialogLanguages: (detected: String, selected: String)?
@ -367,7 +368,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
func processURLs(urls: [URL]) { func processURLs(urls: [URL]) {
isMediasLoading = true isMediasLoading = true
let items = urls.filter { $0.startAccessingSecurityScopedResource() } let items = urls.filter { $0.startAccessingSecurityScopedResource() }
.compactMap { NSItemProvider(contentsOf: $0) } .compactMap { NSItemProvider(contentsOf: $0) }
processItemsProvider(items: items) processItemsProvider(items: items)
} }
@ -391,7 +392,8 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
error: nil)) error: nil))
} else if let content = content as? ImageFileTranseferable, } else if let content = content as? ImageFileTranseferable,
let compressedData = await compressor.compressImageFrom(url: content.url), let compressedData = await compressor.compressImageFrom(url: content.url),
let image = UIImage(data: compressedData) { let image = UIImage(data: compressedData)
{
mediasImages.append(.init(image: image, mediasImages.append(.init(image: image,
movieTransferable: nil, movieTransferable: nil,
gifTransferable: nil, gifTransferable: nil,
@ -616,7 +618,8 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
} }
} else if let videoURL = originalContainer.movieTransferable?.url, } else if let videoURL = originalContainer.movieTransferable?.url,
let compressedVideoURL = await compressor.compressVideo(videoURL), let compressedVideoURL = await compressor.compressVideo(videoURL),
let data = try? Data(contentsOf: compressedVideoURL) { let data = try? Data(contentsOf: compressedVideoURL)
{
let uploadedMedia = try await uploadMedia(data: data, mimeType: compressedVideoURL.mimeType()) let uploadedMedia = try await uploadMedia(data: data, mimeType: compressedVideoURL.mimeType())
mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil, mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil,
movieTransferable: originalContainer.movieTransferable, movieTransferable: originalContainer.movieTransferable,

View file

@ -16,7 +16,8 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
public init(fetcher: Fetcher, public init(fetcher: Fetcher,
client: Client, client: Client,
routerPath: RouterPath, routerPath: RouterPath,
isRemote: Bool = false) { isRemote: Bool = false)
{
self.fetcher = fetcher self.fetcher = fetcher
self.isRemote = isRemote self.isRemote = isRemote
self.client = client self.client = client
@ -33,13 +34,14 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
case .error: case .error:
ErrorView(title: "status.error.title", ErrorView(title: "status.error.title",
message: "status.error.loading.message", message: "status.error.loading.message",
buttonTitle: "action.retry") { buttonTitle: "action.retry")
{
Task { Task {
await fetcher.fetchNewestStatuses() await fetcher.fetchNewestStatuses()
} }
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
case let .display(statuses, nextPageState): case let .display(statuses, nextPageState):
ForEach(statuses, id: \.viewId) { status in ForEach(statuses, id: \.viewId) { status in
@ -48,14 +50,14 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
routerPath: routerPath, routerPath: routerPath,
isRemote: isRemote) isRemote: isRemote)
}) })
.id(status.id) .id(status.id)
.onAppear { .onAppear {
fetcher.statusDidAppear(status: status) fetcher.statusDidAppear(status: status)
} }
.onDisappear { .onDisappear {
fetcher.statusDidDisappear(status: status) fetcher.statusDidDisappear(status: status)
} }
} }
switch nextPageState { switch nextPageState {
case .hasNextPage: case .hasNextPage:

View file

@ -22,7 +22,8 @@ class VideoPlayerViewModel: ObservableObject {
} }
guard let player else { return } guard let player else { return }
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem, queue: .main) { [weak self] _ in object: player.currentItem, queue: .main)
{ [weak self] _ in
if autoPlay { if autoPlay {
self?.player?.seek(to: CMTime.zero) self?.player?.seek(to: CMTime.zero)
self?.player?.play() self?.player?.play()

View file

@ -1,10 +1,10 @@
import Combine import Combine
import DesignSystem
import Env import Env
import Models import Models
import NaturalLanguage import NaturalLanguage
import Network import Network
import SwiftUI import SwiftUI
import DesignSystem
@MainActor @MainActor
public class StatusRowViewModel: ObservableObject { public class StatusRowViewModel: ObservableObject {
@ -39,10 +39,11 @@ public class StatusRowViewModel: ObservableObject {
recalcCollapse() recalcCollapse()
} }
} }
// number of lines to show, nil means show the whole post // number of lines to show, nil means show the whole post
@Published var lineLimit: Int? = nil @Published var lineLimit: Int? = nil
// post length determining if the post should be collapsed // post length determining if the post should be collapsed
let collapseThresholdLength : Int = 750 let collapseThresholdLength: Int = 750
// number of text lines to show on a collpased post // number of text lines to show on a collpased post
let collapsedLines: Int = 8 let collapsedLines: Int = 8
// user preference, set in init // user preference, set in init
@ -51,7 +52,7 @@ public class StatusRowViewModel: ObservableObject {
private func recalcCollapse() { private func recalcCollapse() {
let hasContentWarning = !status.spoilerText.asRawText.isEmpty let hasContentWarning = !status.spoilerText.asRawText.isEmpty
let showCollapseButton = collapseLongPosts && isCollapsed && !hasContentWarning let showCollapseButton = collapseLongPosts && isCollapsed && !hasContentWarning
&& finalStatus.content.asRawText.unicodeScalars.count > collapseThresholdLength && finalStatus.content.asRawText.unicodeScalars.count > collapseThresholdLength
let newlineLimit = showCollapseButton && isCollapsed ? collapsedLines : nil let newlineLimit = showCollapseButton && isCollapsed ? collapsedLines : nil
if newlineLimit != lineLimit { if newlineLimit != lineLimit {
lineLimit = newlineLimit lineLimit = newlineLimit
@ -94,7 +95,7 @@ public class StatusRowViewModel: ObservableObject {
textDisabled: Bool = false) textDisabled: Bool = false)
{ {
self.status = status self.status = status
self.finalStatus = status.reblog ?? status finalStatus = status.reblog ?? status
self.client = client self.client = client
self.routerPath = routerPath self.routerPath = routerPath
self.isFocused = isFocused self.isFocused = isFocused
@ -112,7 +113,6 @@ public class StatusRowViewModel: ObservableObject {
displaySpoiler = !finalStatus.spoilerText.asRawText.isEmpty displaySpoiler = !finalStatus.spoilerText.asRawText.isEmpty
} }
if status.mentions.first(where: { $0.id == CurrentAccount.shared.account?.id }) != nil { if status.mentions.first(where: { $0.id == CurrentAccount.shared.account?.id }) != nil {
userMentionned = true userMentionned = true
} else { } else {
@ -187,7 +187,8 @@ public class StatusRowViewModel: ObservableObject {
if !content.statusesURLs.isEmpty, if !content.statusesURLs.isEmpty,
let url = content.statusesURLs.first, let url = content.statusesURLs.first,
!StatusEmbedCache.shared.badStatusesURLs.contains(url), !StatusEmbedCache.shared.badStatusesURLs.contains(url),
client.hasConnection(with: url) { client.hasConnection(with: url)
{
return url return url
} }
return nil return nil
@ -224,8 +225,7 @@ public class StatusRowViewModel: ObservableObject {
} }
if let embed { if let embed {
StatusEmbedCache.shared.set(url: url, status: embed) StatusEmbedCache.shared.set(url: url, status: embed)
} } else {
else {
StatusEmbedCache.shared.badStatusesURLs.insert(url) StatusEmbedCache.shared.badStatusesURLs.insert(url)
} }
withAnimation { withAnimation {

View file

@ -24,7 +24,7 @@ struct StatusRowActionsView: View {
// Main actor-isolated static property 'allCases' cannot be used to // Main actor-isolated static property 'allCases' cannot be used to
// satisfy nonisolated protocol requirement // satisfy nonisolated protocol requirement
// //
nonisolated public static var allCases: [StatusRowActionsView.Action] { public nonisolated static var allCases: [StatusRowActionsView.Action] {
[.respond, .boost, .favorite, .bookmark, .share] [.respond, .boost, .favorite, .bookmark, .share]
} }
@ -99,7 +99,8 @@ struct StatusRowActionsView: View {
{ {
ShareLink(item: url, ShareLink(item: url,
subject: Text(viewModel.finalStatus.account.safeDisplayName), subject: Text(viewModel.finalStatus.account.safeDisplayName),
message: Text(viewModel.finalStatus.content.asRawText)) { message: Text(viewModel.finalStatus.content.asRawText))
{
action.image(dataController: statusDataController) action.image(dataController: statusDataController)
} }
.buttonStyle(.statusAction()) .buttonStyle(.statusAction())
@ -142,7 +143,8 @@ struct StatusRowActionsView: View {
(viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id)) (viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id))
if let count = action.count(dataController: statusDataController, if let count = action.count(dataController: statusDataController,
viewModel: viewModel, viewModel: viewModel,
theme: theme), !viewModel.isRemote { theme: theme), !viewModel.isRemote
{
Text("\(count)") Text("\(count)")
.foregroundColor(Color(UIColor.secondaryLabel)) .foregroundColor(Color(UIColor.secondaryLabel))
.font(.scaledFootnote) .font(.scaledFootnote)
@ -151,7 +153,6 @@ struct StatusRowActionsView: View {
} }
} }
private func handleAction(action: Action) { private func handleAction(action: Action) {
Task { Task {
if viewModel.isRemote, viewModel.localStatusId == nil || viewModel.localStatus == nil { if viewModel.isRemote, viewModel.localStatusId == nil || viewModel.localStatus == nil {

View file

@ -71,7 +71,8 @@ struct StatusRowContextMenu: View {
{ {
ShareLink(item: url, ShareLink(item: url,
subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName), subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName),
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) { message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText))
{
Label("status.action.share", systemImage: "square.and.arrow.up") Label("status.action.share", systemImage: "square.and.arrow.up")
} }

View file

@ -124,7 +124,8 @@ public struct StatusRowMediaPreviewView: View {
} }
} }
.alert("status.editor.media.image-description", .alert("status.editor.media.image-description",
isPresented: $isAltAlertDisplayed) { isPresented: $isAltAlertDisplayed)
{
Button("alert.button.ok", action: {}) Button("alert.button.ok", action: {})
} message: { } message: {
Text(altTextDisplayed ?? "") Text(altTextDisplayed ?? "")

View file

@ -109,9 +109,9 @@ struct StatusRowSwipeView: View {
isBookmarked: statusDataController.isBookmarked, isBookmarked: statusDataController.isBookmarked,
privateBoost: privateBoost), privateBoost: privateBoost),
imageNamed: action.iconName(isReblogged: statusDataController.isReblogged, imageNamed: action.iconName(isReblogged: statusDataController.isReblogged,
isFavorited: statusDataController.isFavorited, isFavorited: statusDataController.isFavorited,
isBookmarked: statusDataController.isBookmarked, isBookmarked: statusDataController.isBookmarked,
privateBoost: privateBoost)) privateBoost: privateBoost))
.labelStyle(.iconOnly) .labelStyle(.iconOnly)
.environment(\.symbolVariants, .none) .environment(\.symbolVariants, .none)
case .iconWithText: case .iconWithText:
@ -120,9 +120,9 @@ struct StatusRowSwipeView: View {
isBookmarked: statusDataController.isBookmarked, isBookmarked: statusDataController.isBookmarked,
privateBoost: privateBoost), privateBoost: privateBoost),
imageNamed: action.iconName(isReblogged: statusDataController.isReblogged, imageNamed: action.iconName(isReblogged: statusDataController.isReblogged,
isFavorited: statusDataController.isFavorited, isFavorited: statusDataController.isFavorited,
isBookmarked: statusDataController.isBookmarked, isBookmarked: statusDataController.isBookmarked,
privateBoost: privateBoost)) privateBoost: privateBoost))
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
.environment(\.symbolVariants, .none) .environment(\.symbolVariants, .none)
} }

View file

@ -34,7 +34,7 @@ public actor TimelineCache {
try await engine.removeAllData() try await engine.removeAllData()
let itemKeys = statuses.map { CacheKey($0[keyPath: \.id]) } let itemKeys = statuses.map { CacheKey($0[keyPath: \.id]) }
let dataAndKeys = try zip(itemKeys, statuses) let dataAndKeys = try zip(itemKeys, statuses)
.map { (key: $0, data: try encoder.encode($1)) } .map { try (key: $0, data: encoder.encode($1)) }
try await engine.write(dataAndKeys) try await engine.write(dataAndKeys)
} catch {} } catch {}
} }

View file

@ -9,7 +9,7 @@ actor TimelineDatasource {
} }
func get() -> [Status] { func get() -> [Status] {
statuses.filter{ $0.filtered?.first?.filter.filterAction != .hide } statuses.filter { $0.filtered?.first?.filter.filterAction != .hide }
} }
func reset() { func reset() {