mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-16 17:55:13 +00:00
format .
This commit is contained in:
parent
2d988d48c1
commit
1f858414d8
146 changed files with 1610 additions and 1637 deletions
|
@ -49,7 +49,7 @@ struct AppView: View {
|
||||||
} else if UIDevice.current.userInterfaceIdiom == .vision {
|
} else if UIDevice.current.userInterfaceIdiom == .vision {
|
||||||
return Tab.visionOSTab()
|
return Tab.visionOSTab()
|
||||||
}
|
}
|
||||||
return sidebarTabs.tabs.map{ $0.tab }
|
return sidebarTabs.tabs.map { $0.tab }
|
||||||
}
|
}
|
||||||
|
|
||||||
var tabBarView: some View {
|
var tabBarView: some View {
|
||||||
|
|
|
@ -72,8 +72,8 @@ private struct SafariRouter: ViewModifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
|
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
|
||||||
var windowScene: UIWindowScene?
|
var windowScene: UIWindowScene?
|
||||||
let viewController: UIViewController = .init()
|
let viewController: UIViewController = .init()
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
@ -123,7 +123,7 @@ private struct SafariRouter: ViewModifier {
|
||||||
window = nil
|
window = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private struct WindowReader: UIViewRepresentable {
|
private struct WindowReader: UIViewRepresentable {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -71,7 +71,7 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
||||||
case .links:
|
case .links:
|
||||||
NavigationTab { TrendingLinksListView(cards: []) }
|
NavigationTab { TrendingLinksListView(cards: []) }
|
||||||
case .post:
|
case .post:
|
||||||
VStack { }
|
VStack {}
|
||||||
case .other:
|
case .other:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ struct TimelineTab: View {
|
||||||
}
|
}
|
||||||
switch newValue {
|
switch newValue {
|
||||||
case let .tagGroup(title, _, _):
|
case let .tagGroup(title, _, _):
|
||||||
if let group = tagGroups.first(where: { $0.title == title}) {
|
if let group = tagGroups.first(where: { $0.title == title }) {
|
||||||
selectedTagGroup = group
|
selectedTagGroup = group
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -212,7 +212,7 @@ struct TimelineTab: View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var pinButton: some View {
|
private var pinButton: some View {
|
||||||
let index = pinnedFilters.firstIndex(where: { $0.id == timeline.id})
|
let index = pinnedFilters.firstIndex(where: { $0.id == timeline.id })
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
if let index {
|
if let index {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ extension NotificationService {
|
||||||
var _plaintext: Data?
|
var _plaintext: Data?
|
||||||
do {
|
do {
|
||||||
_plaintext = try AES.GCM.open(sealedBox, using: key)
|
_plaintext = try AES.GCM.open(sealedBox, using: key)
|
||||||
} catch { }
|
} catch {}
|
||||||
guard let plaintext = _plaintext else {
|
guard let plaintext = _plaintext else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -37,7 +37,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.unblock(id: account.id))
|
viewModel.relationship = try await client.post(endpoint: Accounts.unblock(id: account.id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.unblock", systemImage: "person.crop.circle.badge.exclamationmark")
|
Label("account.action.unblock", systemImage: "person.crop.circle.badge.exclamationmark")
|
||||||
|
@ -55,7 +55,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.unmute(id: account.id))
|
viewModel.relationship = try await client.post(endpoint: Accounts.unmute(id: account.id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.unmute", systemImage: "speaker")
|
Label("account.action.unmute", systemImage: "speaker")
|
||||||
|
@ -67,7 +67,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.mute(id: account.id, json: MuteData(duration: duration.rawValue)))
|
viewModel.relationship = try await client.post(endpoint: Accounts.mute(id: account.id, json: MuteData(duration: duration.rawValue)))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
||||||
notify: false,
|
notify: false,
|
||||||
reblogs: relationship.showingReblogs))
|
reblogs: relationship.showingReblogs))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.notify-disable", systemImage: "bell.fill")
|
Label("account.action.notify-disable", systemImage: "bell.fill")
|
||||||
|
@ -98,7 +98,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
||||||
notify: true,
|
notify: true,
|
||||||
reblogs: relationship.showingReblogs))
|
reblogs: relationship.showingReblogs))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.notify-enable", systemImage: "bell")
|
Label("account.action.notify-enable", systemImage: "bell")
|
||||||
|
@ -111,7 +111,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
||||||
notify: relationship.notifying,
|
notify: relationship.notifying,
|
||||||
reblogs: false))
|
reblogs: false))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.reboosts-hide", image: "Rocket.Fill")
|
Label("account.action.reboosts-hide", image: "Rocket.Fill")
|
||||||
|
@ -123,7 +123,7 @@ public struct AccountDetailContextMenu: View {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
|
||||||
notify: relationship.notifying,
|
notify: relationship.notifying,
|
||||||
reblogs: true))
|
reblogs: true))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.reboosts-show", image: "Rocket")
|
Label("account.action.reboosts-show", image: "Rocket")
|
||||||
|
|
|
@ -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")
|
||||||
|
@ -370,7 +368,7 @@ public struct AccountDetailView: View {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
viewModel.relationship = try await client.post(endpoint: Accounts.block(id: account.id))
|
viewModel.relationship = try await client.post(endpoint: Accounts.block(id: account.id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -166,7 +166,7 @@ import SwiftUI
|
||||||
pinned: nil))
|
pinned: nil))
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
||||||
if selectedTab == .boosts {
|
if selectedTab == .boosts {
|
||||||
boosts = statuses.filter{ $0.reblog != nil }
|
boosts = statuses.filter { $0.reblog != nil }
|
||||||
}
|
}
|
||||||
if selectedTab == .statuses {
|
if selectedTab == .statuses {
|
||||||
pinned =
|
pinned =
|
||||||
|
@ -206,8 +206,8 @@ import SwiftUI
|
||||||
pinned: nil))
|
pinned: nil))
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -14,7 +14,7 @@ public struct EditAccountView: View {
|
||||||
|
|
||||||
@State private var viewModel = EditAccountViewModel()
|
@State private var viewModel = EditAccountViewModel()
|
||||||
|
|
||||||
public init() { }
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,12 +205,12 @@ struct ConversationMessageView: View {
|
||||||
.frame(height: 200)
|
.frame(height: 200)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
#if targetEnvironment(macCatalyst) || os(visionOS)
|
#if targetEnvironment(macCatalyst) || os(visionOS)
|
||||||
openWindow(value: WindowDestinationMedia.mediaViewer(attachments: [attachement],
|
openWindow(value: WindowDestinationMedia.mediaViewer(attachments: [attachement],
|
||||||
selectedAttachment: attachement))
|
selectedAttachment: attachement))
|
||||||
#else
|
#else
|
||||||
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
|
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,4 +201,3 @@ public struct ThreadsLight: ColorSet {
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,6 @@ public class SceneDelegate: NSObject, UIWindowSceneDelegate, Sendable {
|
||||||
delegate.windowHeight = newHeight
|
delegate.windowHeight = newHeight
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import SwiftUI
|
||||||
public struct CancelToolbarItem: ToolbarContent {
|
public struct CancelToolbarItem: ToolbarContent {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
public init() { }
|
public init() {}
|
||||||
|
|
||||||
public var body: some ToolbarContent {
|
public var body: some ToolbarContent {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,6 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public var alwaysUseDeepl: Bool {
|
public var alwaysUseDeepl: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
storage.alwaysUseDeepl = alwaysUseDeepl
|
storage.alwaysUseDeepl = alwaysUseDeepl
|
||||||
|
@ -415,7 +414,7 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
|
|
||||||
public var totalNotificationsCount: Int {
|
public var totalNotificationsCount: Int {
|
||||||
notificationsCount.compactMap{ $0.value }.reduce(0, +)
|
notificationsCount.compactMap { $0.value }.reduce(0, +)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reloadNotificationsCount(tokens: [OauthToken]) {
|
public func reloadNotificationsCount(tokens: [OauthToken]) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
enum DisplayType {
|
enum DisplayType {
|
||||||
case image
|
case image
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ public struct StreamEventUpdate: StreamEvent {
|
||||||
|
|
||||||
public struct StreamEventStatusUpdate: StreamEvent {
|
public struct StreamEventStatusUpdate: StreamEvent {
|
||||||
public let date = Date()
|
public let date = Date()
|
||||||
public var id: String { status.id + (status.editedAt?.asDate.description ?? "")}
|
public var id: String { status.id + (status.editedAt?.asDate.description ?? "") }
|
||||||
public let status: Status
|
public let status: Status
|
||||||
public init(status: Status) {
|
public init(status: Status) {
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ import SwiftUI
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let _: Marker = try await client.post(endpoint: Markers.markNotifications(lastReadId: id))
|
let _: Marker = try await client.post(endpoint: Markers.markNotifications(lastReadId: id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,5 +37,4 @@ extension StatusEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
#if !os(visionOS) && !DEBUG
|
#if !os(visionOS) && !DEBUG
|
||||||
import GiphyUISDK
|
import GiphyUISDK
|
||||||
#endif
|
#endif
|
||||||
import Models
|
import Models
|
||||||
import NukeUI
|
import NukeUI
|
||||||
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -35,5 +35,4 @@ extension StatusEditor {
|
||||||
Coordinator(picker: self)
|
Coordinator(picker: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,4 @@ extension StatusEditor {
|
||||||
let categoryName: String
|
let categoryName: String
|
||||||
var emojis: [Emoji]
|
var emojis: [Emoji]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ 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 {
|
||||||
case noData
|
case noData
|
||||||
|
@ -106,5 +106,4 @@ extension StatusEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#if !os(visionOS) && !DEBUG
|
#if !os(visionOS) && !DEBUG
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import GiphyUISDK
|
import GiphyUISDK
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct GifPickerView: UIViewControllerRepresentable {
|
struct GifPickerView: UIViewControllerRepresentable {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
var completion: (String) -> Void
|
var completion: (String) -> Void
|
||||||
|
@ -49,5 +49,5 @@ struct GifPickerView: UIViewControllerRepresentable {
|
||||||
parent.completion(url ?? "")
|
parent.completion(url ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -13,5 +13,4 @@ extension StatusEditor {
|
||||||
let mediaAttachment: MediaAttachment?
|
let mediaAttachment: MediaAttachment?
|
||||||
let error: Error?
|
let error: Error?
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,5 +171,4 @@ extension StatusEditor {
|
||||||
return translation?.content.asRawText
|
return translation?.content.asRawText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,5 +249,4 @@ extension StatusEditor {
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,5 +121,4 @@ extension StatusEditor {
|
||||||
return index == count - 1 && count < maxEntries
|
return index == count - 1 && count < maxEntries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -49,5 +49,4 @@ extension StatusEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,4 @@ extension StatusEditor {
|
||||||
enum EditorFocusState: Hashable {
|
enum EditorFocusState: Hashable {
|
||||||
case main, followUp(index: UUID)
|
case main, followUp(index: UUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
public enum StatusEditor { }
|
public enum StatusEditor {}
|
||||||
|
|
|
@ -30,5 +30,4 @@ extension StatusEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = []
|
||||||
|
@ -838,7 +839,7 @@ extension StatusEditor {
|
||||||
error: nil
|
error: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
try? await Task.sleep(for: .seconds(5))
|
try? await Task.sleep(for: .seconds(5))
|
||||||
} while !Task.isCancelled
|
} while !Task.isCancelled
|
||||||
|
@ -859,7 +860,7 @@ extension StatusEditor {
|
||||||
mediaAttachment: media,
|
mediaAttachment: media,
|
||||||
error: nil
|
error: nil
|
||||||
)
|
)
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ public struct StatusEditHistoryView: View {
|
||||||
.task {
|
.task {
|
||||||
do {
|
do {
|
||||||
history = try await client.get(endpoint: Statuses.history(id: statusId))
|
history = try await client.get(endpoint: Statuses.history(id: statusId))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ public struct StatusPollView: View {
|
||||||
// Make sure they're all the same width using a ZStack with 100% hiding behind the
|
// Make sure they're all the same width using a ZStack with 100% hiding behind the
|
||||||
// real percentage.
|
// real percentage.
|
||||||
Text("100%").hidden().overlay(alignment: .trailing) {
|
Text("100%").hidden().overlay(alignment: .trailing) {
|
||||||
Text("\(absolutePercent(for:option.votesCount ?? 0))%")
|
Text("\(absolutePercent(for: option.votesCount ?? 0))%")
|
||||||
.font(.scaledSubheadline)
|
.font(.scaledSubheadline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ public struct StatusPollView: View {
|
||||||
return Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(viewModel.poll.options.count)") +
|
return Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(viewModel.poll.options.count)") +
|
||||||
Text(", ") +
|
Text(", ") +
|
||||||
Text(option.title) +
|
Text(option.title) +
|
||||||
Text(showPercentage ? ", \(absolutePercent(for:option.votesCount ?? 0))%" : "")
|
Text(showPercentage ? ", \(absolutePercent(for: option.votesCount ?? 0))%" : "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private var footerView: some View {
|
private var footerView: some View {
|
||||||
|
@ -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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import SwiftUI
|
||||||
votes = poll.ownVotes ?? []
|
votes = poll.ownVotes ?? []
|
||||||
showResults = true
|
showResults = true
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func handleSelection(_ pollIndex: Int) {
|
public func handleSelection(_ pollIndex: Int) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,13 +197,14 @@ 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 {
|
||||||
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
||||||
viewModel.authorRelationship = try await client.post(endpoint: Accounts.block(id: operationAccount.id))
|
viewModel.authorRelationship = try await client.post(endpoint: Accounts.block(id: operationAccount.id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,4 +360,3 @@ public struct StatusRowView: View {
|
||||||
.environment(PushNotificationsService.shared)
|
.environment(PushNotificationsService.shared)
|
||||||
.environment(QuickLook.shared)
|
.environment(QuickLook.shared)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -156,7 +157,7 @@ public struct StatusRowCardView: View {
|
||||||
.foregroundColor(theme.tintColor)
|
.foregroundColor(theme.tintColor)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
if let history = card.history {
|
if let history = card.history {
|
||||||
let uses = history.compactMap{ Int($0.accounts )}.reduce(0, +)
|
let uses = history.compactMap { Int($0.accounts) }.reduce(0, +)
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Image(systemName: "bubble.left.and.text.bubble.right")
|
Image(systemName: "bubble.left.and.text.bubble.right")
|
||||||
Text("trending-tag-people-talking \(uses)")
|
Text("trending-tag-people-talking \(uses)")
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -204,7 +204,7 @@ struct StatusRowContextMenu: View {
|
||||||
do {
|
do {
|
||||||
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
||||||
viewModel.authorRelationship = try await client.post(endpoint: Accounts.unmute(id: operationAccount.id))
|
viewModel.authorRelationship = try await client.post(endpoint: Accounts.unmute(id: operationAccount.id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.unmute", systemImage: "speaker")
|
Label("account.action.unmute", systemImage: "speaker")
|
||||||
|
@ -217,7 +217,7 @@ struct StatusRowContextMenu: View {
|
||||||
do {
|
do {
|
||||||
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
||||||
viewModel.authorRelationship = try await client.post(endpoint: Accounts.mute(id: operationAccount.id, json: MuteData(duration: duration.rawValue)))
|
viewModel.authorRelationship = try await client.post(endpoint: Accounts.mute(id: operationAccount.id, json: MuteData(duration: duration.rawValue)))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +271,7 @@ struct StatusRowContextMenu: View {
|
||||||
do {
|
do {
|
||||||
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account
|
||||||
viewModel.authorRelationship = try await client.post(endpoint: Accounts.unblock(id: operationAccount.id))
|
viewModel.authorRelationship = try await client.post(endpoint: Accounts.unblock(id: operationAccount.id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.action.unblock", systemImage: "person.crop.circle.badge.exclamationmark")
|
Label("account.action.unblock", systemImage: "person.crop.circle.badge.exclamationmark")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -23,13 +23,13 @@ public struct StatusRowMediaPreviewView: View {
|
||||||
self.sensitive = sensitive
|
self.sensitive = sensitive
|
||||||
}
|
}
|
||||||
|
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
private var showsScrollIndicators: Bool { attachments.count > 1 }
|
private var showsScrollIndicators: Bool { attachments.count > 1 }
|
||||||
private var scrollBottomPadding: CGFloat?
|
private var scrollBottomPadding: CGFloat?
|
||||||
#else
|
#else
|
||||||
private var showsScrollIndicators: Bool = false
|
private var showsScrollIndicators: Bool = false
|
||||||
private var scrollBottomPadding: CGFloat? = 0
|
private var scrollBottomPadding: CGFloat? = 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private var imageMaxHeight: CGFloat {
|
private var imageMaxHeight: CGFloat {
|
||||||
if isCompact {
|
if isCompact {
|
||||||
|
@ -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) }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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, _):
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,7 +355,7 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
|
|
||||||
// High chance the user is scrolled to the top.
|
// High chance the user is scrolled to the top.
|
||||||
// We need to update the statuses state, and then scroll to the previous top most status.
|
// We need to update the statuses state, and then scroll to the previous top most status.
|
||||||
if let topStatus, visibileStatuses.contains(where: { $0.id == topStatus.id}), scrollToTopVisible {
|
if let topStatus, visibileStatuses.contains(where: { $0.id == topStatus.id }), scrollToTopVisible {
|
||||||
pendingStatusesObserver.disableUpdate = true
|
pendingStatusesObserver.disableUpdate = true
|
||||||
let statuses = await datasource.getFiltered()
|
let statuses = await datasource.getFiltered()
|
||||||
statusesState = .display(statuses: statuses,
|
statusesState = .display(statuses: statuses,
|
||||||
|
@ -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 {
|
||||||
|
@ -469,7 +469,7 @@ extension TimelineViewModel {
|
||||||
guard let id = await cache.getLatestSeenStatus(for: client, filter: timeline.id)?.first else { return }
|
guard let id = await cache.getLatestSeenStatus(for: client, filter: timeline.id)?.first else { return }
|
||||||
do {
|
do {
|
||||||
let _: Marker = try await client.post(endpoint: Markers.markHome(lastReadId: id))
|
let _: Marker = try await client.post(endpoint: Markers.markHome(lastReadId: id))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,9 +73,9 @@ public actor TimelineCache {
|
||||||
func setLatestSeenStatuses(_ statuses: [Status], for client: Client, filter: String) {
|
func setLatestSeenStatuses(_ statuses: [Status], for client: Client, filter: String) {
|
||||||
let statuses = statuses.sorted(by: { $0.createdAt.asDate > $1.createdAt.asDate })
|
let statuses = statuses.sorted(by: { $0.createdAt.asDate > $1.createdAt.asDate })
|
||||||
if filter == "Home" {
|
if filter == "Home" {
|
||||||
UserDefaults.standard.set(statuses.map{ $0.id }, forKey: "timeline-last-seen-\(client.id)")
|
UserDefaults.standard.set(statuses.map { $0.id }, forKey: "timeline-last-seen-\(client.id)")
|
||||||
} else {
|
} else {
|
||||||
UserDefaults.standard.set(statuses.map{ $0.id }, forKey: "timeline-last-seen-\(client.id)-\(filter)")
|
UserDefaults.standard.set(statuses.map { $0.id }, forKey: "timeline-last-seen-\(client.id)-\(filter)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue