This commit is contained in:
Thomas Ricouard 2024-02-14 12:48:14 +01:00
parent 2d988d48c1
commit 1f858414d8
146 changed files with 1610 additions and 1637 deletions

View file

@ -1,7 +1,7 @@
import SwiftUI
import Env
import AppAccount import AppAccount
import DesignSystem import DesignSystem
import Env
import SwiftUI
@MainActor @MainActor
struct NavigationSheet<Content: View>: View { struct NavigationSheet<Content: View>: View {

View file

@ -1,8 +1,8 @@
import SwiftUI
import Env
import AppAccount import AppAccount
import DesignSystem import DesignSystem
import Env
import Network import Network
import SwiftUI
@MainActor @MainActor
struct NavigationTab<Content: View>: View { struct NavigationTab<Content: View>: View {

View file

@ -60,9 +60,9 @@ struct NotificationsTab: View {
} }
} }
} }
.onChange(of: selectedTab, { _, newValue in .onChange(of: selectedTab) { _, _ in
clearNotifications() clearNotifications()
}) }
.onChange(of: pushNotificationsService.handledNotification) { _, newValue in .onChange(of: pushNotificationsService.handledNotification) { _, newValue in
if let newValue, let type = newValue.notification.supportedType { if let newValue, let type = newValue.notification.supportedType {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {

View file

@ -5,8 +5,8 @@ import Models
import Network import Network
import NukeUI import NukeUI
import SwiftUI import SwiftUI
import UserNotifications
import Timeline import Timeline
import UserNotifications
@MainActor @MainActor
struct ContentSettingsView: View { struct ContentSettingsView: View {

View file

@ -1,8 +1,8 @@
import SwiftUI
import SwiftData
import Models
import Env
import DesignSystem import DesignSystem
import Env
import Models
import SwiftData
import SwiftUI
struct RecenTagsSettingView: View { struct RecenTagsSettingView: View {
@Environment(\.modelContext) private var context @Environment(\.modelContext) private var context

View file

@ -1,8 +1,8 @@
import SwiftUI
import SwiftData
import Models
import Env
import DesignSystem import DesignSystem
import Env
import Models
import SwiftData
import SwiftUI
struct RemoteTimelinesSettingView: View { struct RemoteTimelinesSettingView: View {
@Environment(\.modelContext) private var context @Environment(\.modelContext) private var context

View file

@ -1,8 +1,8 @@
import SwiftUI
import SwiftData
import Models
import Env
import DesignSystem import DesignSystem
import Env
import Models
import SwiftData
import SwiftUI
struct TagsGroupSettingView: View { struct TagsGroupSettingView: View {
@Environment(\.modelContext) private var context @Environment(\.modelContext) private var context

View file

@ -114,7 +114,6 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
Label("explore.section.trending.links", systemImage: iconName) Label("explore.section.trending.links", systemImage: iconName)
case .other: case .other:
EmptyView() EmptyView()
} }
} }

View file

@ -1,7 +1,7 @@
import SwiftUI
import Env
import AppAccount import AppAccount
import DesignSystem import DesignSystem
import Env
import SwiftUI
@MainActor @MainActor
struct ToolbarTab: ToolbarContent { struct ToolbarTab: ToolbarContent {
@ -17,7 +17,8 @@ struct ToolbarTab: ToolbarContent {
statusEditorToolbarItem(routerPath: routerPath, statusEditorToolbarItem(routerPath: routerPath,
visibility: userPreferences.postVisibility) visibility: userPreferences.postVisibility)
if UIDevice.current.userInterfaceIdiom != .pad || if UIDevice.current.userInterfaceIdiom != .pad ||
(UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass == .compact) { (UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass == .compact)
{
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
AppAccountsSelectorView(routerPath: routerPath) AppAccountsSelectorView(routerPath: routerPath)
} }

View file

@ -2,11 +2,11 @@ import Account
import AppAccount import AppAccount
import DesignSystem import DesignSystem
import Env import Env
import Models
import Network import Network
import StatusKit import StatusKit
import SwiftUI import SwiftUI
import UIKit import UIKit
import Models
class ShareViewController: UIViewController { class ShareViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {

View file

@ -31,7 +31,7 @@ let package = Package(
.product(name: "Models", package: "Models"), .product(name: "Models", package: "Models"),
.product(name: "StatusKit", package: "StatusKit"), .product(name: "StatusKit", package: "StatusKit"),
.product(name: "Env", package: "Env"), .product(name: "Env", package: "Env"),
.product(name: "ButtonKit", package: "ButtonKit") .product(name: "ButtonKit", package: "ButtonKit"),
], ],
swiftSettings: [ swiftSettings: [
.enableExperimentalFeature("StrictConcurrency"), .enableExperimentalFeature("StrictConcurrency"),

View file

@ -220,7 +220,6 @@ public struct AccountDetailView: View {
AvatarView(account.avatar, config: .badge) AvatarView(account.avatar, config: .badge)
.padding(.leading, -4) .padding(.leading, -4)
.accessibilityLabel(account.safeDisplayName) .accessibilityLabel(account.safeDisplayName)
} }
.accessibilityAddTraits(.isImage) .accessibilityAddTraits(.isImage)
.buttonStyle(.plain) .buttonStyle(.plain)
@ -288,7 +287,6 @@ public struct AccountDetailView: View {
routerPath.presentedSheet = .mentionStatusEditor(account: account, routerPath.presentedSheet = .mentionStatusEditor(account: account,
visibility: preferences.postVisibility) visibility: preferences.postVisibility)
#endif #endif
} }
} label: { } label: {
Image(systemName: "arrowshape.turn.up.left") Image(systemName: "arrowshape.turn.up.left")

View file

@ -151,7 +151,7 @@ import SwiftUI
self.familiarFollowers = familiarFollowers?.first?.accounts ?? [] self.familiarFollowers = familiarFollowers?.first?.accounts ?? []
} }
func fetchNewestStatuses(pullToRefresh: Bool) async { func fetchNewestStatuses(pullToRefresh _: Bool) async {
guard let client else { return } guard let client else { return }
do { do {
statusesState = .loading statusesState = .loading
@ -207,7 +207,7 @@ import SwiftUI
statuses.append(contentsOf: newStatuses) statuses.append(contentsOf: newStatuses)
if selectedTab == .boosts { if selectedTab == .boosts {
let newBoosts = statuses.filter { $0.reblog != nil } let newBoosts = statuses.filter { $0.reblog != nil }
self.boosts.append(contentsOf: newBoosts) boosts.append(contentsOf: newBoosts)
} }
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client) StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
if selectedTab == .boosts { if selectedTab == .boosts {
@ -253,7 +253,8 @@ import SwiftUI
if let event = event as? StreamEventUpdate { if let event = event as? StreamEventUpdate {
if event.status.account.id == currentAccount.account?.id { if event.status.account.id == currentAccount.account?.id {
if (event.status.inReplyToId == nil && selectedTab == .statuses) || if (event.status.inReplyToId == nil && selectedTab == .statuses) ||
(event.status.inReplyToId != nil && selectedTab == .replies) { (event.status.inReplyToId != nil && selectedTab == .replies)
{
statuses.insert(event.status, at: 0) statuses.insert(event.status, at: 0)
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage) statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
} }

View file

@ -1,8 +1,8 @@
import Models import Models
import Network import Network
import Observation import Observation
import SwiftUI
import OSLog import OSLog
import SwiftUI
public enum AccountsListMode { public enum AccountsListMode {
case following(accountId: String), followers(accountId: String) case following(accountId: String), followers(accountId: String)
@ -144,8 +144,6 @@ public enum AccountsListMode {
relationships: relationships, relationships: relationships,
nextPageState: .none) nextPageState: .none)
} }
} catch { } catch {}
}
} }
} }

View file

@ -2,8 +2,8 @@ import DesignSystem
import Env import Env
import Models import Models
import Network import Network
import SwiftUI
import NukeUI import NukeUI
import SwiftUI
@MainActor @MainActor
public struct EditAccountView: View { public struct EditAccountView: View {

View file

@ -1,9 +1,9 @@
import Models import Models
import Network import Network
import Observation import Observation
import SwiftUI
import PhotosUI import PhotosUI
import StatusKit import StatusKit
import SwiftUI
@MainActor @MainActor
@Observable class EditAccountViewModel { @Observable class EditAccountViewModel {
@ -33,12 +33,13 @@ import StatusKit
var isPhotoPickerPresented: Bool = false { var isPhotoPickerPresented: Bool = false {
didSet { didSet {
if !isPhotoPickerPresented && mediaPickers.isEmpty { if !isPhotoPickerPresented, mediaPickers.isEmpty {
isChangingAvatar = false isChangingAvatar = false
isChangingHeader = false isChangingHeader = false
} }
} }
} }
var isChangingAvatar: Bool = false var isChangingAvatar: Bool = false
var isChangingHeader: Bool = false var isChangingHeader: Bool = false

View file

@ -4,8 +4,8 @@ import Foundation
import Models import Models
import Network import Network
import Observation import Observation
import SwiftUI
import OSLog import OSLog
import SwiftUI
@MainActor @MainActor
@Observable public class FollowButtonViewModel { @Observable public class FollowButtonViewModel {

View file

@ -1,7 +1,7 @@
import DesignSystem import DesignSystem
import Env
import Models import Models
import SwiftUI import SwiftUI
import Env
public struct ListsListView: View { public struct ListsListView: View {
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@ -43,4 +43,3 @@ public struct ListsListView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
} }

View file

@ -1,9 +1,9 @@
import StatusKit import DesignSystem
import Network
import SwiftUI
import Env import Env
import Models import Models
import DesignSystem import Network
import StatusKit
import SwiftUI
@MainActor @MainActor
public struct AccountStatusesListView: View { public struct AccountStatusesListView: View {

View file

@ -1,8 +1,8 @@
import SwiftUI
import Models
import StatusKit
import Network
import Env import Env
import Models
import Network
import StatusKit
import SwiftUI
@MainActor @MainActor
@Observable @Observable
@ -40,7 +40,7 @@ public class AccountStatusesListViewModel: StatusesFetcher {
self.mode = mode self.mode = mode
} }
public func fetchNewestStatuses(pullToRefresh: Bool) async { public func fetchNewestStatuses(pullToRefresh _: Bool) async {
guard let client else { return } guard let client else { return }
statusesState = .loading statusesState = .loading
do { do {
@ -63,11 +63,7 @@ public class AccountStatusesListViewModel: StatusesFetcher {
nextPageState: nextPage?.maxId != nil ? .hasNextPage : .none) nextPageState: nextPage?.maxId != nil ? .hasNextPage : .none)
} }
public func statusDidAppear(status: Status) { public func statusDidAppear(status _: Status) {}
} public func statusDidDisappear(status _: Status) {}
public func statusDidDisappear(status: Status) {
}
} }

View file

@ -1,7 +1,7 @@
import DesignSystem import DesignSystem
import Env
import Models import Models
import SwiftUI import SwiftUI
import Env
public struct FollowedTagsListView: View { public struct FollowedTagsListView: View {
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@ -32,4 +32,3 @@ public struct FollowedTagsListView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
} }

View file

@ -201,4 +201,3 @@ public struct ThreadsLight: ColorSet {
public init() {} public init() {}
} }

View file

@ -70,7 +70,6 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
delegate.windowHeight = newHeight delegate.windowHeight = newHeight
} }
#endif #endif
} }
} }
} }

View file

@ -197,7 +197,7 @@ import SwiftUI
// better against the tintColor // better against the tintColor
private func computeContrastingTintColor() { private func computeContrastingTintColor() {
func luminance(_ color: Color.Resolved) -> Float { func luminance(_ color: Color.Resolved) -> Float {
return 0.299 * color.red + 0.587 * color.green + 0.114 * color.blue; return 0.299 * color.red + 0.587 * color.green + 0.114 * color.blue
} }
let resolvedTintColor = tintColor.resolve(in: .init()) let resolvedTintColor = tintColor.resolve(in: .init())
@ -340,7 +340,7 @@ import SwiftUI
ConstellationLight(), ConstellationLight(),
ConstellationDark(), ConstellationDark(),
ThreadsLight(), ThreadsLight(),
ThreadsDark() ThreadsDark(),
] ]
} }

View file

@ -77,16 +77,14 @@ struct ThemeApplier: ViewModifier {
} }
private func setWindowUserInterfaceStyle(_ userInterfaceStyle: UIUserInterfaceStyle) { private func setWindowUserInterfaceStyle(_ userInterfaceStyle: UIUserInterfaceStyle) {
allWindows() for window in allWindows() {
.forEach { window.overrideUserInterfaceStyle = userInterfaceStyle
$0.overrideUserInterfaceStyle = userInterfaceStyle
} }
} }
private func setWindowTint(_ color: Color) { private func setWindowTint(_ color: Color) {
allWindows() for window in allWindows() {
.forEach { window.tintColor = UIColor(color)
$0.tintColor = UIColor(color)
} }
} }

View file

@ -4,7 +4,7 @@ public struct NextPageView: View {
@State private var isLoadingNextPage: Bool = false @State private var isLoadingNextPage: Bool = false
@State private var showRetry: Bool = false @State private var showRetry: Bool = false
let loadNextPage: (() async throws -> Void) let loadNextPage: () async throws -> Void
public init(loadNextPage: @escaping (() async throws -> Void)) { public init(loadNextPage: @escaping (() async throws -> Void)) {
self.loadNextPage = loadNextPage self.loadNextPage = loadNextPage

View file

@ -148,7 +148,8 @@ public enum SheetDestination: Identifiable {
return .handled return .handled
} else if url.lastPathComponent.first == "@", } else if url.lastPathComponent.first == "@",
let host = url.host, let host = url.host,
!host.hasPrefix("www") { !host.hasPrefix("www")
{
let acct = "\(url.lastPathComponent)@\(host)" let acct = "\(url.lastPathComponent)@\(host)"
Task { Task {
await navigateToAccountFrom(acct: acct, url: url) await navigateToAccountFrom(acct: acct, url: url)

View file

@ -65,7 +65,7 @@ import OSLog
connect() connect()
} }
watchedStreams = streams watchedStreams = streams
streams.forEach { stream in for stream in streams {
sendMessage(message: StreamMessage(type: "subscribe", stream: stream.rawValue)) sendMessage(message: StreamMessage(type: "subscribe", stream: stream.rawValue))
} }
} }
@ -159,19 +159,19 @@ import OSLog
public func emmitDeleteEvent(for status: String) { public func emmitDeleteEvent(for status: String) {
let event = StreamEventDelete(status: status) let event = StreamEventDelete(status: status)
self.events.append(event) events.append(event)
self.latestEvent = event latestEvent = event
} }
public func emmitEditEvent(for status: Status) { public func emmitEditEvent(for status: Status) {
let event = StreamEventStatusUpdate(status: status) let event = StreamEventStatusUpdate(status: status)
self.events.append(event) events.append(event)
self.latestEvent = event latestEvent = event
} }
public func emmitPostEvent(for status: Status) { public func emmitPostEvent(for status: Status) {
let event = StreamEventUpdate(status: status) let event = StreamEventUpdate(status: status)
self.events.append(event) events.append(event)
self.latestEvent = event latestEvent = event
} }
} }

View file

@ -183,7 +183,6 @@ import SwiftUI
} }
} }
public var alwaysUseDeepl: Bool { public var alwaysUseDeepl: Bool {
didSet { didSet {
storage.alwaysUseDeepl = alwaysUseDeepl storage.alwaysUseDeepl = alwaysUseDeepl

View file

@ -1,7 +1,7 @@
@testable import Env @testable import Env
import XCTest
import SwiftUI
import Network import Network
import SwiftUI
import XCTest
@MainActor @MainActor
final class RouterTests: XCTestCase { final class RouterTests: XCTestCase {

View file

@ -1,8 +1,8 @@
import DesignSystem import DesignSystem
import Models import Models
import Network
import StatusKit import StatusKit
import SwiftUI import SwiftUI
import Network
public struct TrendingLinksListView: View { public struct TrendingLinksListView: View {
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme

View file

@ -1,5 +1,5 @@
import SwiftUI
import Models import Models
import SwiftUI
enum DisplayType { enum DisplayType {
case image case image

View file

@ -1,9 +1,9 @@
import AVKit import AVKit
import DesignSystem import DesignSystem
import Env import Env
import Models
import Observation import Observation
import SwiftUI import SwiftUI
import Models
@MainActor @MainActor
@Observable public class MediaUIAttachmentVideoViewModel { @Observable public class MediaUIAttachmentVideoViewModel {
@ -23,7 +23,7 @@ import Models
#if !os(visionOS) #if !os(visionOS)
player?.preventsDisplaySleepDuringVideoPlayback = false player?.preventsDisplaySleepDuringVideoPlayback = false
#endif #endif
if (autoPlay || forceAutoPlay) && !isCompact { if autoPlay || forceAutoPlay, !isCompact {
player?.play() player?.play()
isPlaying = true isPlaying = true
} else { } else {
@ -179,7 +179,8 @@ public struct MediaUIAttachmentVideoView: View {
!viewModel.forceAutoPlay, !viewModel.forceAutoPlay,
!isFullScreen, !isFullScreen,
!viewModel.isPlaying, !viewModel.isPlaying,
!isCompact { !isCompact
{
Button(action: { Button(action: {
viewModel.play() viewModel.play()
}, label: { }, label: {

View file

@ -1,8 +1,8 @@
import AVFoundation
import Models import Models
import Nuke import Nuke
import QuickLook import QuickLook
import SwiftUI import SwiftUI
import AVFoundation
public struct MediaUIView: View, @unchecked Sendable { public struct MediaUIView: View, @unchecked Sendable {
private let data: [DisplayData] private let data: [DisplayData]

View file

@ -1,6 +1,6 @@
import SwiftUI
import NukeUI
import Nuke import Nuke
import NukeUI
import SwiftUI
struct QuickLookToolbarItem: ToolbarContent, @unchecked Sendable { struct QuickLookToolbarItem: ToolbarContent, @unchecked Sendable {
let itemUrl: URL let itemUrl: URL

View file

@ -92,9 +92,9 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
self.discoverable = discoverable self.discoverable = discoverable
if let displayName, !displayName.isEmpty { if let displayName, !displayName.isEmpty {
self.cachedDisplayName = .init(stringValue: displayName) cachedDisplayName = .init(stringValue: displayName)
} else { } else {
self.cachedDisplayName = .init(stringValue: "@\(username)") cachedDisplayName = .init(stringValue: "@\(username)")
} }
} }
@ -122,29 +122,29 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id) id = try container.decode(String.self, forKey: .id)
self.username = try container.decode(String.self, forKey: .username) username = try container.decode(String.self, forKey: .username)
self.displayName = try container.decodeIfPresent(String.self, forKey: .displayName) displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
self.avatar = try container.decode(URL.self, forKey: .avatar) avatar = try container.decode(URL.self, forKey: .avatar)
self.header = try container.decode(URL.self, forKey: .header) header = try container.decode(URL.self, forKey: .header)
self.acct = try container.decode(String.self, forKey: .acct) acct = try container.decode(String.self, forKey: .acct)
self.note = try container.decode(HTMLString.self, forKey: .note) note = try container.decode(HTMLString.self, forKey: .note)
self.createdAt = try container.decode(ServerDate.self, forKey: .createdAt) createdAt = try container.decode(ServerDate.self, forKey: .createdAt)
self.followersCount = try container.decodeIfPresent(Int.self, forKey: .followersCount) followersCount = try container.decodeIfPresent(Int.self, forKey: .followersCount)
self.followingCount = try container.decodeIfPresent(Int.self, forKey: .followingCount) followingCount = try container.decodeIfPresent(Int.self, forKey: .followingCount)
self.statusesCount = try container.decodeIfPresent(Int.self, forKey: .statusesCount) statusesCount = try container.decodeIfPresent(Int.self, forKey: .statusesCount)
self.lastStatusAt = try container.decodeIfPresent(String.self, forKey: .lastStatusAt) lastStatusAt = try container.decodeIfPresent(String.self, forKey: .lastStatusAt)
self.fields = try container.decode([Account.Field].self, forKey: .fields) fields = try container.decode([Account.Field].self, forKey: .fields)
self.locked = try container.decode(Bool.self, forKey: .locked) locked = try container.decode(Bool.self, forKey: .locked)
self.emojis = try container.decode([Emoji].self, forKey: .emojis) emojis = try container.decode([Emoji].self, forKey: .emojis)
self.url = try container.decodeIfPresent(URL.self, forKey: .url) url = try container.decodeIfPresent(URL.self, forKey: .url)
self.source = try container.decodeIfPresent(Account.Source.self, forKey: .source) source = try container.decodeIfPresent(Account.Source.self, forKey: .source)
self.bot = try container.decode(Bool.self, forKey: .bot) bot = try container.decode(Bool.self, forKey: .bot)
self.discoverable = try container.decodeIfPresent(Bool.self, forKey: .discoverable) discoverable = try container.decodeIfPresent(Bool.self, forKey: .discoverable)
if let displayName, !displayName.isEmpty { if let displayName, !displayName.isEmpty {
self.cachedDisplayName = .init(stringValue: displayName) cachedDisplayName = .init(stringValue: displayName)
} else { } else {
self.cachedDisplayName = .init(stringValue: "@\(username)") cachedDisplayName = .init(stringValue: "@\(username)")
} }
} }

View file

@ -3,8 +3,8 @@ import Foundation
import Models import Models
import Observation import Observation
import os import os
import SwiftUI
import OSLog import OSLog
import SwiftUI
@Observable public final class Client: Equatable, Identifiable, Hashable, @unchecked Sendable { @Observable public final class Client: Equatable, Identifiable, Hashable, @unchecked Sendable {
public static func == (lhs: Client, rhs: Client) -> Bool { public static func == (lhs: Client, rhs: Client) -> Bool {
@ -286,7 +286,8 @@ import OSLog
method: String, method: String,
mimeType: String, mimeType: String,
filename: String, filename: String,
data: Data) throws -> URLRequest { data: Data) throws -> URLRequest
{
let url = try makeURL(endpoint: endpoint, forceVersion: version) let url = try makeURL(endpoint: endpoint, forceVersion: version)
var request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method) var request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
let boundary = UUID().uuidString let boundary = UUID().uuidString

View file

@ -34,8 +34,8 @@ public struct InstanceSocialClient: Sendable {
} }
} }
extension Array where Self.Element == InstanceSocial { private extension Array where Self.Element == InstanceSocial {
fileprivate func sorted(by keyword: String) -> Self { func sorted(by keyword: String) -> Self {
let keyword = keyword.trimmingCharacters(in: .whitespacesAndNewlines) let keyword = keyword.trimmingCharacters(in: .whitespacesAndNewlines)
var newArray = self var newArray = self

View file

@ -37,5 +37,4 @@ extension StatusEditor {
} }
} }
} }
} }

View file

@ -159,7 +159,6 @@ extension StatusEditor {
.accessibilityLabel("accessibility.editor.button.attach-photo") .accessibilityLabel("accessibility.editor.button.attach-photo")
.disabled(viewModel.showPoll) .disabled(viewModel.showPoll)
Button { Button {
// all SEVM have the same visibility value // all SEVM have the same visibility value
followUpSEVMs.append(ViewModel(mode: .new(visibility: focusedSEVM.visibility))) followUpSEVMs.append(ViewModel(mode: .new(visibility: focusedSEVM.visibility)))
@ -188,7 +187,6 @@ extension StatusEditor {
} }
} }
if preferences.isOpenAIEnabled { if preferences.isOpenAIEnabled {
AIMenu.disabled(!viewModel.canPost) AIMenu.disabled(!viewModel.canPost)
} }
@ -268,7 +266,5 @@ extension StatusEditor {
} }
} }
} }
} }
} }

View file

@ -1,12 +1,11 @@
import DesignSystem import DesignSystem
import EmojiText import EmojiText
import Foundation import Foundation
import SwiftUI
import Models import Models
import SwiftData import SwiftData
import SwiftUI
extension StatusEditor { extension StatusEditor {
@MainActor @MainActor
struct AutoCompleteView: View { struct AutoCompleteView: View {
@Environment(\.modelContext) var context @Environment(\.modelContext) var context
@ -22,7 +21,8 @@ extension StatusEditor {
var body: some View { var body: some View {
if !viewModel.mentionsSuggestions.isEmpty || if !viewModel.mentionsSuggestions.isEmpty ||
!viewModel.tagsSuggestions.isEmpty || !viewModel.tagsSuggestions.isEmpty ||
(viewModel.showRecentsTagsInline && !recentTags.isEmpty) { (viewModel.showRecentsTagsInline && !recentTags.isEmpty)
{
VStack { VStack {
HStack { HStack {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {

View file

@ -1,10 +1,10 @@
import DesignSystem import DesignSystem
import EmojiText import EmojiText
import Env
import Foundation import Foundation
import SwiftUI
import Models import Models
import SwiftData import SwiftData
import Env import SwiftUI
extension StatusEditor.AutoCompleteView { extension StatusEditor.AutoCompleteView {
@MainActor @MainActor

View file

@ -1,10 +1,9 @@
import DesignSystem import DesignSystem
import EmojiText import EmojiText
import Foundation import Foundation
import SwiftUI
import Models import Models
import SwiftData import SwiftData
import SwiftUI
extension StatusEditor.AutoCompleteView { extension StatusEditor.AutoCompleteView {
struct MentionsView: View { struct MentionsView: View {

View file

@ -1,10 +1,9 @@
import DesignSystem import DesignSystem
import EmojiText import EmojiText
import Foundation import Foundation
import SwiftUI
import Models import Models
import SwiftData import SwiftData
import SwiftUI
extension StatusEditor.AutoCompleteView { extension StatusEditor.AutoCompleteView {
struct RecentTagsView: View { struct RecentTagsView: View {

View file

@ -1,10 +1,9 @@
import DesignSystem import DesignSystem
import EmojiText import EmojiText
import Foundation import Foundation
import SwiftUI
import Models import Models
import SwiftData import SwiftData
import SwiftUI
extension StatusEditor.AutoCompleteView { extension StatusEditor.AutoCompleteView {
struct RemoteTagsView: View { struct RemoteTagsView: View {

View file

@ -35,5 +35,4 @@ extension StatusEditor {
Coordinator(picker: self) Coordinator(picker: self)
} }
} }
} }

View file

@ -7,5 +7,4 @@ extension StatusEditor {
let categoryName: String let categoryName: String
var emojis: [Emoji] var emojis: [Emoji]
} }
} }

View file

@ -2,8 +2,8 @@ import AVFoundation
import Foundation import Foundation
import UIKit import UIKit
extension StatusEditor { public extension StatusEditor {
public actor Compressor { actor Compressor {
public init() {} public init() {}
enum CompressorError: Error { enum CompressorError: Error {
@ -106,5 +106,4 @@ extension StatusEditor {
} }
} }
} }
} }

View file

@ -1,11 +1,10 @@
import DesignSystem import DesignSystem
import Env import Env
import SwiftUI
import Models import Models
import NukeUI import NukeUI
import SwiftUI
extension StatusEditor { extension StatusEditor {
@MainActor @MainActor
struct CustomEmojisView: View { struct CustomEmojisView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss

View file

@ -1,10 +1,9 @@
import DesignSystem import DesignSystem
import Env import Env
import SwiftUI
import Models import Models
import SwiftUI
extension StatusEditor { extension StatusEditor {
@MainActor @MainActor
struct LangButton: View { struct LangButton: View {
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme

View file

@ -13,5 +13,4 @@ extension StatusEditor {
let mediaAttachment: MediaAttachment? let mediaAttachment: MediaAttachment?
let error: Error? let error: Error?
} }
} }

View file

@ -171,5 +171,4 @@ extension StatusEditor {
return translation?.content.asRawText return translation?.content.asRawText
} }
} }
} }

View file

@ -249,5 +249,4 @@ extension StatusEditor {
.cornerRadius(8) .cornerRadius(8)
} }
} }
} }

View file

@ -121,5 +121,4 @@ extension StatusEditor {
return index == count - 1 && count < maxEntries return index == count - 1 && count < maxEntries
} }
} }
} }

View file

@ -86,10 +86,10 @@ extension StatusEditor {
static var transferRepresentation: some TransferRepresentation { static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .movie) { receivedTransferrable in FileRepresentation(importedContentType: .movie) { receivedTransferrable in
return MovieFileTranseferable(url: receivedTransferrable.localURL) MovieFileTranseferable(url: receivedTransferrable.localURL)
} }
FileRepresentation(importedContentType: .video) { receivedTransferrable in FileRepresentation(importedContentType: .video) { receivedTransferrable in
return MovieFileTranseferable(url: receivedTransferrable.localURL) MovieFileTranseferable(url: receivedTransferrable.localURL)
} }
} }
} }
@ -112,7 +112,7 @@ extension StatusEditor {
static var transferRepresentation: some TransferRepresentation { static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .gif) { receivedTransferrable in FileRepresentation(importedContentType: .gif) { receivedTransferrable in
return GifFileTranseferable(url: receivedTransferrable.localURL) GifFileTranseferable(url: receivedTransferrable.localURL)
} }
} }
} }
@ -133,7 +133,7 @@ public extension StatusEditor {
public static var transferRepresentation: some TransferRepresentation { public static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .image) { receivedTransferrable in FileRepresentation(importedContentType: .image) { receivedTransferrable in
return ImageFileTranseferable(url: receivedTransferrable.localURL) ImageFileTranseferable(url: receivedTransferrable.localURL)
} }
} }
} }
@ -141,15 +141,15 @@ public extension StatusEditor {
public extension ReceivedTransferredFile { public extension ReceivedTransferredFile {
var localURL: URL { var localURL: URL {
if self.isOriginalFile { if isOriginalFile {
return file return file
} }
let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(self.file.pathExtension)") let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(file.pathExtension)")
try? FileManager.default.copyItem(at: self.file, to: copy) try? FileManager.default.copyItem(at: file, to: copy)
return copy return copy
} }
} }
public extension URL { public extension URL {
func mimeType() -> String { func mimeType() -> String {
if let mimeType = UTType(filenameExtension: pathExtension)?.preferredMIMEType { if let mimeType = UTType(filenameExtension: pathExtension)?.preferredMIMEType {

View file

@ -49,5 +49,4 @@ extension StatusEditor {
} }
} }
} }
} }

View file

@ -4,5 +4,4 @@ extension StatusEditor {
enum EditorFocusState: Hashable { enum EditorFocusState: Hashable {
case main, followUp(index: UUID) case main, followUp(index: UUID)
} }
} }

View file

@ -159,7 +159,6 @@ extension StatusEditor {
} }
} }
@ViewBuilder @ViewBuilder
private var characterCountAndLangView: some View { private var characterCountAndLangView: some View {
let value = (currentInstance.instance?.configuration?.statuses.maxCharacters ?? 500) + viewModel.statusTextCharacterLength let value = (currentInstance.instance?.configuration?.statuses.maxCharacters ?? 500) + viewModel.statusTextCharacterLength
@ -223,5 +222,4 @@ extension StatusEditor {
Task { await viewModel.fetchCustomEmojis() } Task { await viewModel.fetchCustomEmojis() }
} }
} }
} }

View file

@ -10,9 +10,9 @@ import StoreKit
import SwiftUI import SwiftUI
import UIKit import UIKit
extension StatusEditor { public extension StatusEditor {
@MainActor @MainActor
public struct MainView: View { struct MainView: View {
@Environment(AppAccountsManager.self) private var appAccounts @Environment(AppAccountsManager.self) private var appAccounts
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme
@ -151,5 +151,4 @@ extension StatusEditor {
.presentationBackgroundInteraction(.enabled) .presentationBackgroundInteraction(.enabled)
} }
} }
} }

View file

@ -30,5 +30,4 @@ extension StatusEditor {
} }
} }
} }
} }

View file

@ -1,8 +1,8 @@
import DesignSystem
import Env import Env
import Models import Models
import StoreKit import StoreKit
import SwiftUI import SwiftUI
import DesignSystem
extension StatusEditor { extension StatusEditor {
@MainActor @MainActor

View file

@ -7,10 +7,9 @@ import Network
import PhotosUI import PhotosUI
import SwiftUI import SwiftUI
extension StatusEditor { public extension StatusEditor {
@MainActor @MainActor
@Observable public class ViewModel: NSObject, Identifiable { @Observable class ViewModel: NSObject, Identifiable {
public let id = UUID() public let id = UUID()
var mode: Mode var mode: Mode
@ -132,15 +131,16 @@ extension StatusEditor {
} }
var allMediaHasDescription: Bool { var allMediaHasDescription: Bool {
var everyMediaHasAltText: Bool = true; var everyMediaHasAltText = true
mediaContainers.forEach { mediaContainer in for mediaContainer in mediaContainers {
if (((mediaContainer.mediaAttachment?.description) == nil) || if ((mediaContainer.mediaAttachment?.description) == nil) ||
mediaContainer.mediaAttachment?.description?.count == 0) { mediaContainer.mediaAttachment?.description?.count == 0
{
everyMediaHasAltText = false everyMediaHasAltText = false
} }
} }
return everyMediaHasAltText; return everyMediaHasAltText
} }
var shouldDisplayDismissWarning: Bool { var shouldDisplayDismissWarning: Bool {
@ -200,12 +200,12 @@ extension StatusEditor {
func postStatus() async -> Status? { func postStatus() async -> Status? {
guard let client else { return nil } guard let client else { return nil }
do { do {
if (!allMediaHasDescription && UserPreferences.shared.appRequireAltText) { if !allMediaHasDescription && UserPreferences.shared.appRequireAltText {
throw PostError.missingAltText throw PostError.missingAltText
} }
if postingTimer == nil { if postingTimer == nil {
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
Task { @MainActor in Task { @MainActor in
if self.postingProgress < 100 { if self.postingProgress < 100 {
self.postingProgress += 0.5 self.postingProgress += 0.5
@ -616,7 +616,8 @@ extension StatusEditor {
if !tagsSuggestions.isEmpty || if !tagsSuggestions.isEmpty ||
!mentionsSuggestions.isEmpty || !mentionsSuggestions.isEmpty ||
currentSuggestionRange != nil || currentSuggestionRange != nil ||
showRecentsTagsInline { showRecentsTagsInline
{
withAnimation { withAnimation {
tagsSuggestions = [] tagsSuggestions = []
mentionsSuggestions = [] mentionsSuggestions = []

View file

@ -8,7 +8,7 @@ private func stripToPureLanguage(inText: String) -> String {
var resultStr = inText var resultStr = inText
[hashtagRegex, emojiRegex, atRegex].forEach { regex in for regex in [hashtagRegex, emojiRegex, atRegex] {
let splitArray = resultStr.split(separator: regex, omittingEmptySubsequences: true) let splitArray = resultStr.split(separator: regex, omittingEmptySubsequences: true)
resultStr = splitArray.joined() as String resultStr = splitArray.joined() as String
} }

View file

@ -188,19 +188,20 @@ public struct StatusPollView: View {
private struct _PercentWidthLayout: Layout { private struct _PercentWidthLayout: Layout {
let percent: CGFloat let percent: CGFloat
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) -> CGSize {
guard let view = subviews.first else { return CGSize.zero } guard let view = subviews.first else { return CGSize.zero }
return view.sizeThatFits(proposal) return view.sizeThatFits(proposal)
} }
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) {
guard let view = subviews.first, guard let view = subviews.first,
let width = proposal.width let width = proposal.width
else { return } else { return }
view.place( view.place(
at: bounds.origin, at: bounds.origin,
proposal: ProposedViewSize(width: percent / 100 * width, height: proposal.height)) proposal: ProposedViewSize(width: percent / 100 * width, height: proposal.height)
)
} }
} }
} }

View file

@ -1,5 +1,5 @@
import SwiftUI
import Models import Models
import SwiftUI
/// A utility that creates a suitable combined accessibility label for a `StatusRowView` that is not focused. /// A utility that creates a suitable combined accessibility label for a `StatusRowView` that is not focused.
@MainActor @MainActor

View file

@ -28,7 +28,7 @@ public struct StatusRowView: View {
private let context: Context private let context: Context
public init(viewModel: StatusRowViewModel, context: Context = .timeline) { public init(viewModel: StatusRowViewModel, context: Context = .timeline) {
self._viewModel = .init(initialValue: viewModel) _viewModel = .init(initialValue: viewModel)
self.context = context self.context = context
} }
@ -100,7 +100,8 @@ public struct StatusRowView: View {
} }
if !reasons.contains(.placeholder), if !reasons.contains(.placeholder),
viewModel.showActions, isFocused || theme.statusActionsDisplay != .none, viewModel.showActions, isFocused || theme.statusActionsDisplay != .none,
!isInCaptureMode { !isInCaptureMode
{
StatusRowActionsView(isBlockConfirmationPresented: $isBlockConfirmationPresented, StatusRowActionsView(isBlockConfirmationPresented: $isBlockConfirmationPresented,
viewModel: viewModel) viewModel: viewModel)
.tint(isFocused ? theme.tintColor : .gray) .tint(isFocused ? theme.tintColor : .gray)
@ -196,7 +197,8 @@ public struct StatusRowView: View {
) )
}) })
.confirmationDialog("", .confirmationDialog("",
isPresented: $isBlockConfirmationPresented) { isPresented: $isBlockConfirmationPresented)
{
Button("account.action.block", role: .destructive) { Button("account.action.block", role: .destructive) {
Task { Task {
do { do {
@ -358,4 +360,3 @@ public struct StatusRowView: View {
.environment(PushNotificationsService.shared) .environment(PushNotificationsService.shared)
.environment(QuickLook.shared) .environment(QuickLook.shared)
} }

View file

@ -1,9 +1,9 @@
import DesignSystem import DesignSystem
import Env
import Models import Models
import Nuke import Nuke
import NukeUI import NukeUI
import SwiftUI import SwiftUI
import Env
@MainActor @MainActor
public struct StatusRowCardView: View { public struct StatusRowCardView: View {
@ -53,10 +53,11 @@ public struct StatusRowCardView: View {
let sitesWithIcons = ["apps.apple.com", "music.apple.com", "podcasts.apple.com", "open.spotify.com"] let sitesWithIcons = ["apps.apple.com", "music.apple.com", "podcasts.apple.com", "open.spotify.com"]
if isCompact { if isCompact {
compactLinkPreview(title, url) compactLinkPreview(title, url)
} else if (UIDevice.current.userInterfaceIdiom == .pad || } else if UIDevice.current.userInterfaceIdiom == .pad ||
UIDevice.current.userInterfaceIdiom == .mac || UIDevice.current.userInterfaceIdiom == .mac ||
UIDevice.current.userInterfaceIdiom == .vision), UIDevice.current.userInterfaceIdiom == .vision,
let host = url.host(), sitesWithIcons.contains(host) { let host = url.host(), sitesWithIcons.contains(host)
{
iconLinkPreview(title, url) iconLinkPreview(title, url)
} else { } else {
defaultLinkPreview(title, url) defaultLinkPreview(title, url)
@ -250,12 +251,12 @@ struct DefaultPreviewImage: View {
let originalWidth: CGFloat let originalWidth: CGFloat
let originalHeight: CGFloat let originalHeight: CGFloat
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) -> CGSize {
guard !subviews.isEmpty else { return CGSize.zero } guard !subviews.isEmpty else { return CGSize.zero }
return calculateSize(proposal) return calculateSize(proposal)
} }
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) {
guard let view = subviews.first else { return } guard let view = subviews.first else { return }
let size = calculateSize(proposal) let size = calculateSize(proposal)

View file

@ -1,8 +1,8 @@
import DesignSystem import DesignSystem
import Env import Env
import Models import Models
import SwiftUI
import Network import Network
import SwiftUI
@MainActor @MainActor
struct StatusRowHeaderView: View { struct StatusRowHeaderView: View {
@ -66,7 +66,8 @@ struct StatusRowHeaderView: View {
} }
if !redactionReasons.contains(.placeholder) { if !redactionReasons.contains(.placeholder) {
if (theme.displayFullUsername && theme.avatarPosition == .leading) || if (theme.displayFullUsername && theme.avatarPosition == .leading) ||
theme.avatarPosition == .top { theme.avatarPosition == .top
{
Text("@\(theme.displayFullUsername ? viewModel.finalStatus.account.acct : viewModel.finalStatus.account.username)") Text("@\(theme.displayFullUsername ? viewModel.finalStatus.account.acct : viewModel.finalStatus.account.username)")
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)

View file

@ -468,7 +468,7 @@ private struct FeaturedImagePreView: View {
self.maxSize = maxSize self.maxSize = maxSize
} }
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) -> CGSize {
guard !subviews.isEmpty else { return CGSize.zero } guard !subviews.isEmpty else { return CGSize.zero }
if let maxSize { return maxSize } if let maxSize { return maxSize }
@ -476,7 +476,7 @@ private struct FeaturedImagePreView: View {
return calculateSize(proposal) return calculateSize(proposal)
} }
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache _: inout ()) {
guard let view = subviews.first else { return } guard let view = subviews.first else { return }
let size = if let maxSize { maxSize } else { calculateSize(proposal) } let size = if let maxSize { maxSize } else { calculateSize(proposal) }

View file

@ -14,7 +14,8 @@ struct StatusRowTagView: View {
if isHomeTimeline, if isHomeTimeline,
let tag = viewModel.finalStatus.content.links.first(where: { link in let tag = viewModel.finalStatus.content.links.first(where: { link in
link.type == .hashtag && currentAccount.tags.contains(where: { $0.name.lowercased() == link.title.lowercased() }) link.type == .hashtag && currentAccount.tags.contains(where: { $0.name.lowercased() == link.title.lowercased() })
}) { })
{
Text("#\(tag.title)") Text("#\(tag.title)")
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)

View file

@ -30,7 +30,6 @@ public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable {
} }
public enum TimelineFilter: Hashable, Equatable, Identifiable { public enum TimelineFilter: Hashable, Equatable, Identifiable {
case home, local, federated, trending case home, local, federated, trending
case hashtag(tag: String, accountId: String?) case hashtag(tag: String, accountId: String?)
case tagGroup(title: String, tags: [String], symbolName: String?) case tagGroup(title: String, tags: [String], symbolName: String?)
@ -50,7 +49,6 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable {
default: default:
return title return title
} }
} }
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {

View file

@ -1,9 +1,9 @@
import DesignSystem
import Env import Env
import Foundation import Foundation
import Models import Models
import Observation import Observation
import SwiftUI import SwiftUI
import DesignSystem
@MainActor @MainActor
@Observable class TimelineUnreadStatusesObserver { @Observable class TimelineUnreadStatusesObserver {

View file

@ -1,5 +1,5 @@
import SwiftUI
import DesignSystem import DesignSystem
import SwiftUI
public struct TimelineContentFilterView: View { public struct TimelineContentFilterView: View {
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme

View file

@ -1,5 +1,5 @@
import SwiftUI
import DesignSystem import DesignSystem
import SwiftUI
struct TimelineHeaderView<Content: View>: View { struct TimelineHeaderView<Content: View>: View {
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme

View file

@ -1,8 +1,8 @@
import SwiftUI import DesignSystem
import Env import Env
import Models import Models
import DesignSystem
import Network import Network
import SwiftUI
@MainActor @MainActor
struct TimelineQuickAccessPills: View { struct TimelineQuickAccessPills: View {
@ -25,14 +25,15 @@ struct TimelineQuickAccessPills: View {
} }
.scrollClipDisabled() .scrollClipDisabled()
.scrollIndicators(.never) .scrollIndicators(.never)
.onChange(of: currentAccount.lists, { _, lists in .onChange(of: currentAccount.lists) { _, lists in
guard client.isAuth else { return } guard client.isAuth else { return }
var filters = pinnedFilters var filters = pinnedFilters
for (index, filter) in filters.enumerated() { for (index, filter) in filters.enumerated() {
switch filter { switch filter {
case .list(let list): case let .list(list):
if let accountList = lists.first(where: { $0.id == list.id }), if let accountList = lists.first(where: { $0.id == list.id }),
accountList.title != list.title { accountList.title != list.title
{
filters[index] = .list(list: accountList) filters[index] = .list(list: accountList)
} }
default: default:
@ -40,7 +41,7 @@ struct TimelineQuickAccessPills: View {
} }
} }
pinnedFilters = filters pinnedFilters = filters
}) }
} }
@ViewBuilder @ViewBuilder
@ -87,7 +88,7 @@ struct TimelineQuickAccessPills: View {
private func isFilterSupported(_ filter: TimelineFilter) -> Bool { private func isFilterSupported(_ filter: TimelineFilter) -> Bool {
switch filter { switch filter {
case .list(let list): case let .list(list):
return currentAccount.lists.contains(where: { $0.id == list.id }) return currentAccount.lists.contains(where: { $0.id == list.id })
default: default:
return true return true
@ -100,23 +101,23 @@ struct PillDropDelegate: DropDelegate {
@Binding var items: [TimelineFilter] @Binding var items: [TimelineFilter]
@Binding var draggedItem: TimelineFilter? @Binding var draggedItem: TimelineFilter?
func dropUpdated(info: DropInfo) -> DropProposal? { func dropUpdated(info _: DropInfo) -> DropProposal? {
return DropProposal(operation: .move) return DropProposal(operation: .move)
} }
func performDrop(info: DropInfo) -> Bool { func performDrop(info _: DropInfo) -> Bool {
draggedItem = nil draggedItem = nil
return true return true
} }
func dropEntered(info: DropInfo) { func dropEntered(info _: DropInfo) {
if let draggedItem { if let draggedItem {
let fromIndex = items.firstIndex(of: draggedItem) let fromIndex = items.firstIndex(of: draggedItem)
if let fromIndex { if let fromIndex {
let toIndex = items.firstIndex(of: destinationItem) let toIndex = items.firstIndex(of: destinationItem)
if let toIndex, fromIndex != toIndex { if let toIndex, fromIndex != toIndex {
withAnimation { withAnimation {
self.items.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: (toIndex > fromIndex ? (toIndex + 1) : toIndex)) self.items.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: toIndex > fromIndex ? (toIndex + 1) : toIndex)
} }
} }
} }

View file

@ -1,6 +1,6 @@
import SwiftUI
import Models
import Env import Env
import Models
import SwiftUI
struct TimelineTagGroupheaderView: View { struct TimelineTagGroupheaderView: View {
@Environment(RouterPath.self) private var routerPath @Environment(RouterPath.self) private var routerPath

View file

@ -1,7 +1,7 @@
import SwiftUI
import Models
import DesignSystem import DesignSystem
import Env import Env
import Models
import SwiftUI
struct TimelineTagHeaderView: View { struct TimelineTagHeaderView: View {
@Environment(CurrentAccount.self) private var account @Environment(CurrentAccount.self) private var account

View file

@ -147,19 +147,20 @@ public struct TimelineView: View {
} }
} }
} }
.onChange(of: account.lists, { _, lists in .onChange(of: account.lists) { _, lists in
guard client.isAuth else { return } guard client.isAuth else { return }
switch timeline { switch timeline {
case let .list(list): case let .list(list):
if let accountList = lists.first(where: { $0.id == list.id }), if let accountList = lists.first(where: { $0.id == list.id }),
list.id == accountList.id, list.id == accountList.id,
accountList.title != list.title { accountList.title != list.title
{
timeline = .list(list: accountList) timeline = .list(list: accountList)
} }
default: default:
break break
} }
}) }
.onChange(of: timeline) { oldValue, newValue in .onChange(of: timeline) { oldValue, newValue in
switch newValue { switch newValue {
case let .remoteLocal(server, _): case let .remoteLocal(server, _):

View file

@ -11,7 +11,7 @@ import SwiftUI
var statusesState: StatusesState = .loading var statusesState: StatusesState = .loading
var timeline: TimelineFilter = .federated { var timeline: TimelineFilter = .federated {
willSet { willSet {
if timeline == .home && newValue != .resume { if timeline == .home, newValue != .resume {
saveMarker() saveMarker()
} }
} }
@ -425,7 +425,6 @@ extension TimelineViewModel: StatusesFetcher {
minId: nil, minId: nil,
offset: statuses.count)) offset: statuses.count))
await datasource.append(contentOf: newStatuses) await datasource.append(contentOf: newStatuses)
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client) StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
@ -450,6 +449,7 @@ extension TimelineViewModel: StatusesFetcher {
} }
// MARK: - MARKER // MARK: - MARKER
extension TimelineViewModel { extension TimelineViewModel {
func fetchMarker() async -> Marker.Content? { func fetchMarker() async -> Marker.Content? {
guard let client else { guard let client else {

View file

@ -1,6 +1,6 @@
import Env
import Foundation import Foundation
import Models import Models
import Env
actor TimelineDatasource { actor TimelineDatasource {
private var statuses: [Status] = [] private var statuses: [Status] = []
@ -24,7 +24,8 @@ actor TimelineDatasource {
!showReplies && status.inReplyToId != nil && status.inReplyToAccountId != status.account.id || !showReplies && status.inReplyToId != nil && status.inReplyToAccountId != status.account.id ||
!showBoosts && status.reblog != nil || !showBoosts && status.reblog != nil ||
!showThreads && status.inReplyToAccountId == status.account.id || !showThreads && status.inReplyToAccountId == status.account.id ||
!showQuotePosts && !status.content.statusesURLs.isEmpty { !showQuotePosts && !status.content.statusesURLs.isEmpty
{
return false return false
} }
return true return true

View file

@ -1,8 +1,7 @@
import Models
import Network
@testable import Timeline @testable import Timeline
import XCTest import XCTest
import Network
import Models
final class TimelineFilterTests: XCTestCase { final class TimelineFilterTests: XCTestCase {
func testCodableHome() throws { func testCodableHome() throws {

View file

@ -1,7 +1,7 @@
import Models
import Network
@testable import Timeline @testable import Timeline
import XCTest import XCTest
import Network
import Models
@MainActor @MainActor
final class TimelineViewModelTests: XCTestCase { final class TimelineViewModelTests: XCTestCase {