Run SwiftFormat

This commit is contained in:
Thomas Ricouard 2023-01-22 06:38:30 +01:00
parent 5991641d32
commit a79c5691e0
49 changed files with 234 additions and 237 deletions

View file

@ -14,7 +14,7 @@ struct IceCubesApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@StateObject private var appAccountsManager = AppAccountsManager.shared @StateObject private var appAccountsManager = AppAccountsManager.shared
@StateObject private var currentInstance = CurrentInstance.shared @StateObject private var currentInstance = CurrentInstance.shared
@StateObject private var currentAccount = CurrentAccount.shared @StateObject private var currentAccount = CurrentAccount.shared
@ -28,7 +28,7 @@ struct IceCubesApp: App {
@State private var selectSidebarItem: Tab? = .timeline @State private var selectSidebarItem: Tab? = .timeline
@State private var popToRootTab: Tab = .other @State private var popToRootTab: Tab = .other
@State private var sideBarLoadedTabs: Set<Tab> = Set() @State private var sideBarLoadedTabs: Set<Tab> = Set()
private var availableTabs: [Tab] { private var availableTabs: [Tab] {
appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab() appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab()
} }
@ -172,7 +172,7 @@ struct IceCubesApp: App {
private func refreshPushSubs() { private func refreshPushSubs() {
PushNotificationsService.shared.requestPushNotifications() PushNotificationsService.shared.requestPushNotifications()
} }
@CommandsBuilder @CommandsBuilder
private var appMenu: some Commands { private var appMenu: some Commands {
CommandGroup(replacing: .newItem) { CommandGroup(replacing: .newItem) {
@ -199,7 +199,7 @@ struct IceCubesApp: App {
class AppDelegate: NSObject, UIApplicationDelegate { class AppDelegate: NSObject, UIApplicationDelegate {
let themeObserver = ThemeObserverViewController(nibName: nil, bundle: nil) let themeObserver = ThemeObserverViewController(nibName: nil, bundle: nil)
func application(_: UIApplication, func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
{ {
@ -220,13 +220,12 @@ class AppDelegate: NSObject, UIApplicationDelegate {
} }
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {} func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
} }
class ThemeObserverViewController: UIViewController { class ThemeObserverViewController: UIViewController {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection) super.traitCollectionDidChange(previousTraitCollection)
print(traitCollection.userInterfaceStyle.rawValue) print(traitCollection.userInterfaceStyle.rawValue)
} }
} }

View file

@ -10,7 +10,7 @@ struct SideBarView<Content: View>: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var watcher: StreamWatcher @EnvironmentObject private var watcher: StreamWatcher
@EnvironmentObject private var userPreferences: UserPreferences @EnvironmentObject private var userPreferences: UserPreferences
@Binding var selectedTab: Tab @Binding var selectedTab: Tab
@Binding var popToRootTab: Tab @Binding var popToRootTab: Tab
var tabs: [Tab] var tabs: [Tab]
@ -23,7 +23,7 @@ struct SideBarView<Content: View>: View {
} }
return 0 return 0
} }
private var profileView: some View { private var profileView: some View {
Button { Button {
selectedTab = .profile selectedTab = .profile
@ -35,7 +35,7 @@ struct SideBarView<Content: View>: View {
.frame(width: .sidebarWidth, height: 60) .frame(width: .sidebarWidth, height: 60)
.background(selectedTab == .profile ? theme.secondaryBackgroundColor : .clear) .background(selectedTab == .profile ? theme.secondaryBackgroundColor : .clear)
} }
private func makeIconForTab(tab: Tab) -> some View { private func makeIconForTab(tab: Tab) -> some View {
ZStack(alignment: .topTrailing) { ZStack(alignment: .topTrailing) {
SideBarIcon(systemIconName: tab.iconName, SideBarIcon(systemIconName: tab.iconName,
@ -55,7 +55,7 @@ struct SideBarView<Content: View>: View {
.contentShape(Rectangle()) .contentShape(Rectangle())
.frame(width: .sidebarWidth, height: 50) .frame(width: .sidebarWidth, height: 50)
} }
private var postButton: some View { private var postButton: some View {
Button { Button {
routerPath.presentedSheet = .newStatusEditor(visibility: userPreferences.serverPreferences?.postVisibility ?? .pub) routerPath.presentedSheet = .newStatusEditor(visibility: userPreferences.serverPreferences?.postVisibility ?? .pub)
@ -68,7 +68,7 @@ struct SideBarView<Content: View>: View {
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.keyboardShortcut("n", modifiers: .command) .keyboardShortcut("n", modifiers: .command)
} }
private func makeAccountButton(account: AppAccount) -> some View { private func makeAccountButton(account: AppAccount) -> some View {
Button { Button {
if account.id == appAccounts.currentAccount.id { if account.id == appAccounts.currentAccount.id {
@ -84,9 +84,9 @@ struct SideBarView<Content: View>: View {
.frame(width: .sidebarWidth, height: 50) .frame(width: .sidebarWidth, height: 50)
.padding(.vertical, 8) .padding(.vertical, 8)
.background(selectedTab == .profile && account.id == appAccounts.currentAccount.id ? .background(selectedTab == .profile && account.id == appAccounts.currentAccount.id ?
theme.secondaryBackgroundColor : .clear) theme.secondaryBackgroundColor : .clear)
} }
private var tabsView: some View { private var tabsView: some View {
ForEach(tabs) { tab in ForEach(tabs) { tab in
Button { Button {
@ -107,7 +107,7 @@ struct SideBarView<Content: View>: View {
.background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear) .background(tab == selectedTab ? theme.secondaryBackgroundColor : .clear)
} }
} }
var body: some View { var body: some View {
HStack(spacing: 0) { HStack(spacing: 0) {
ScrollView { ScrollView {
@ -141,12 +141,12 @@ struct SideBarView<Content: View>: View {
private struct SideBarIcon: View { private struct SideBarIcon: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
let systemIconName: String let systemIconName: String
let isSelected: Bool let isSelected: Bool
@State private var isHovered: Bool = false @State private var isHovered: Bool = false
var body: some View { var body: some View {
Image(systemName: systemIconName) Image(systemName: systemIconName)
.resizable() .resizable()

View file

@ -1,10 +1,10 @@
import AppAccount import AppAccount
import Env import Env
import Models
import Network import Network
import Notifications import Notifications
import SwiftUI import SwiftUI
import Timeline import Timeline
import Models
struct NotificationsTab: View { struct NotificationsTab: View {
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@ -13,7 +13,7 @@ struct NotificationsTab: View {
@EnvironmentObject private var userPreferences: UserPreferences @EnvironmentObject private var userPreferences: UserPreferences
@StateObject private var routerPath = RouterPath() @StateObject private var routerPath = RouterPath()
@Binding var popToRootTab: Tab @Binding var popToRootTab: Tab
let lockedType: Models.Notification.NotificationType? let lockedType: Models.Notification.NotificationType?
var body: some View { var body: some View {

View file

@ -5,9 +5,9 @@ import Env
import Models import Models
import Network import Network
import NukeUI import NukeUI
import SafariServices
import Shimmer import Shimmer
import SwiftUI import SwiftUI
import SafariServices
struct AddAccountView: View { struct AddAccountView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -81,7 +81,7 @@ struct AddAccountView: View {
instanceNamePublisher.send(newValue) instanceNamePublisher.send(newValue)
} }
.onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in .onReceive(instanceNamePublisher.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)) { newValue in
let newValue = newValue let newValue = newValue
.replacingOccurrences(of: "http://", with: "") .replacingOccurrences(of: "http://", with: "")
.replacingOccurrences(of: "https://", with: "") .replacingOccurrences(of: "https://", with: "")
let client = Client(server: newValue) let client = Client(server: newValue)
@ -171,8 +171,8 @@ struct AddAccountView: View {
(Text("instance.list.users-\(instance.users)") (Text("instance.list.users-\(instance.users)")
+ Text("") + Text("")
+ Text("instance.list.posts-\(instance.statuses)")) + Text("instance.list.posts-\(instance.statuses)"))
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
@ -235,11 +235,9 @@ struct AddAccountView: View {
struct SafariView: UIViewControllerRepresentable { struct SafariView: UIViewControllerRepresentable {
let url: URL let url: URL
func makeUIViewController(context: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController { func makeUIViewController(context _: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController {
SFSafariViewController(url: url) SFSafariViewController(url: url)
} }
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<SafariView>) {
} func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext<SafariView>) {}
} }

View file

@ -1,8 +1,8 @@
import DesignSystem import DesignSystem
import Env
import Models import Models
import Status import Status
import SwiftUI import SwiftUI
import Env
struct DisplaySettingsView: View { struct DisplaySettingsView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -16,15 +16,15 @@ struct DisplaySettingsView: View {
Toggle("settings.display.theme.systemColor", isOn: $theme.followSystemColorScheme) Toggle("settings.display.theme.systemColor", isOn: $theme.followSystemColorScheme)
themeSelectorButton themeSelectorButton
ColorPicker("settings.display.theme.tint", selection: $theme.tintColor) ColorPicker("settings.display.theme.tint", selection: $theme.tintColor)
.onChange(of: theme.tintColor) { newValue in .onChange(of: theme.tintColor) { _ in
theme.followSystemColorScheme = false theme.followSystemColorScheme = false
} }
ColorPicker("settings.display.theme.background", selection: $theme.primaryBackgroundColor) ColorPicker("settings.display.theme.background", selection: $theme.primaryBackgroundColor)
.onChange(of: theme.primaryBackgroundColor) { newValue in .onChange(of: theme.primaryBackgroundColor) { _ in
theme.followSystemColorScheme = false theme.followSystemColorScheme = false
} }
ColorPicker("settings.display.theme.secondary-background", selection: $theme.secondaryBackgroundColor) ColorPicker("settings.display.theme.secondary-background", selection: $theme.secondaryBackgroundColor)
.onChange(of: theme.primaryBackgroundColor) { newValue in .onChange(of: theme.primaryBackgroundColor) { _ in
theme.followSystemColorScheme = false theme.followSystemColorScheme = false
} }
} }
@ -54,7 +54,7 @@ struct DisplaySettingsView: View {
} }
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
VStack { VStack {
Slider(value: $userPreferences.fontSizeScale, in: 0.5...1.5, step: 0.1) Slider(value: $userPreferences.fontSizeScale, in: 0.5 ... 1.5, step: 0.1)
Text("Font scaling: \(String(format: "%.1f", userPreferences.fontSizeScale))") Text("Font scaling: \(String(format: "%.1f", userPreferences.fontSizeScale))")
.font(.scaledBody) .font(.scaledBody)
} }

View file

@ -134,7 +134,7 @@ struct SettingsTabs: View {
NavigationLink(destination: SupportAppView()) { NavigationLink(destination: SupportAppView()) {
Label("settings.app.support", systemImage: "wand.and.stars") Label("settings.app.support", systemImage: "wand.and.stars")
} }
if let reviewURL = URL(string: "https://apps.apple.com/app/id\(AppInfo.appStoreAppId)?action=write-review") { if let reviewURL = URL(string: "https://apps.apple.com/app/id\(AppInfo.appStoreAppId)?action=write-review") {
Link(destination: reviewURL) { Link(destination: reviewURL) {
Label("Rate Ice Cubes", systemImage: "link") Label("Rate Ice Cubes", systemImage: "link")
@ -142,11 +142,11 @@ struct SettingsTabs: View {
.tint(theme.labelColor) .tint(theme.labelColor)
} }
} header: { } header: {
Text("settings.section.app") Text("settings.section.app")
} footer: { } footer: {
if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
Text("App Version: \(appVersion)").frame(maxWidth: .infinity, alignment: .center) Text("App Version: \(appVersion)").frame(maxWidth: .infinity, alignment: .center)
} }
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }

View file

@ -15,7 +15,7 @@ struct SupportAppView: View {
var productId: String { var productId: String {
"icecubes.tipjar.\(rawValue)" "icecubes.tipjar.\(rawValue)"
} }
var title: LocalizedStringKey { var title: LocalizedStringKey {
switch self { switch self {
case .one: case .one:
@ -26,7 +26,7 @@ struct SupportAppView: View {
return "settings.support.three.title" return "settings.support.three.title"
} }
} }
var subtitle: LocalizedStringKey { var subtitle: LocalizedStringKey {
switch self { switch self {
case .one: case .one:

View file

@ -94,12 +94,12 @@ struct AddRemoteTimelineView: View {
Text(instance.info?.shortDescription ?? "") Text(instance.info?.shortDescription ?? "")
.font(.scaledBody) .font(.scaledBody)
.foregroundColor(.gray) .foregroundColor(.gray)
(Text("instance.list.users-\(instance.users)") (Text("instance.list.users-\(instance.users)")
+ Text("") + Text("")
+ Text("instance.list.posts-\(instance.statuses)")) + Text("instance.list.posts-\(instance.statuses)"))
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)

View file

@ -7,7 +7,7 @@ import Network
import SwiftUI import SwiftUI
import Timeline import Timeline
struct TimelineTab: View { struct TimelineTab: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@ -104,7 +104,7 @@ struct TimelineTab: View {
} }
} }
} }
Menu("timeline.filter.local") { Menu("timeline.filter.local") {
ForEach(preferences.remoteLocalTimelines, id: \.self) { server in ForEach(preferences.remoteLocalTimelines, id: \.self) { server in
Button { Button {

View file

@ -119,8 +119,8 @@ struct AccountDetailHeaderView: View {
relationship: relationship, relationship: relationship,
shouldDisplayNotify: true, shouldDisplayNotify: true,
relationshipUpdated: { relationship in relationshipUpdated: { relationship in
viewModel.relationship = relationship viewModel.relationship = relationship
})) }))
} }
} }
} }

View file

@ -10,7 +10,7 @@ import SwiftUI
public struct AccountDetailView: View { public struct AccountDetailView: View {
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@Environment(\.redactionReasons) private var reasons @Environment(\.redactionReasons) private var reasons
@EnvironmentObject private var watcher: StreamWatcher @EnvironmentObject private var watcher: StreamWatcher
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@ -149,7 +149,7 @@ public struct AccountDetailView: View {
Text("Error: \(error.localizedDescription)") Text("Error: \(error.localizedDescription)")
} }
} }
@ViewBuilder @ViewBuilder
private var joinedAtView: some View { private var joinedAtView: some View {
if let joinedAt = viewModel.account?.createdAt.asDate { if let joinedAt = viewModel.account?.createdAt.asDate {
@ -377,7 +377,7 @@ public struct AccountDetailView: View {
if !viewModel.isCurrentUser { if !viewModel.isCurrentUser {
Button { Button {
routerPath.presentedSheet = .mentionStatusEditor(account: account, routerPath.presentedSheet = .mentionStatusEditor(account: account,
visibility: preferences.serverPreferences?.postVisibility ?? .pub) visibility: preferences.serverPreferences?.postVisibility ?? .pub)
} label: { } label: {
Label("account.action.mention", systemImage: "at") Label("account.action.mention", systemImage: "at")
} }
@ -386,9 +386,9 @@ public struct AccountDetailView: View {
} label: { } label: {
Label("account.action.message", systemImage: "tray.full") Label("account.action.message", systemImage: "tray.full")
} }
Divider() Divider()
if viewModel.relationship?.blocking == true { if viewModel.relationship?.blocking == true {
Button { Button {
Task { Task {

View file

@ -140,7 +140,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
featuredTags: featuredTags, featuredTags: featuredTags,
relationships: []) relationships: [])
} }
func fetchFamilliarFollowers() async { func fetchFamilliarFollowers() async {
let familiarFollowers: [FamiliarAccounts]? = try? await client?.get(endpoint: Accounts.familiarFollowers(withAccount: accountId)) let familiarFollowers: [FamiliarAccounts]? = try? await client?.get(endpoint: Accounts.familiarFollowers(withAccount: accountId))
self.familiarFollowers = familiarFollowers?.first?.accounts ?? [] self.familiarFollowers = familiarFollowers?.first?.accounts ?? []

View file

@ -5,7 +5,7 @@ import SwiftUI
public enum AccountsListMode { public enum AccountsListMode {
case following(accountId: String), followers(accountId: String) case following(accountId: String), followers(accountId: String)
case favouritedBy(statusId: String), rebloggedBy(statusId: String) case favouritedBy(statusId: String), rebloggedBy(statusId: String)
var title: LocalizedStringKey { var title: LocalizedStringKey {
switch self { switch self {
case .following: case .following:

View file

@ -9,14 +9,15 @@ public class FollowButtonViewModel: ObservableObject {
public let accountId: String public let accountId: String
public let shouldDisplayNotify: Bool public let shouldDisplayNotify: Bool
public let relationshipUpdated: ((Relationship) -> Void) public let relationshipUpdated: (Relationship) -> Void
@Published public private(set) var relationship: Relationship @Published public private(set) var relationship: Relationship
@Published public private(set) var isUpdating: Bool = false @Published public private(set) var isUpdating: Bool = false
public init(accountId: String, public init(accountId: String,
relationship: Relationship, relationship: Relationship,
shouldDisplayNotify: Bool, shouldDisplayNotify: Bool,
relationshipUpdated: @escaping ((Relationship) -> Void)) { relationshipUpdated: @escaping ((Relationship) -> Void))
{
self.accountId = accountId self.accountId = accountId
self.relationship = relationship self.relationship = relationship
self.shouldDisplayNotify = shouldDisplayNotify self.shouldDisplayNotify = shouldDisplayNotify

View file

@ -26,7 +26,7 @@ public struct AppAccountView: View {
} }
} }
} }
@ViewBuilder @ViewBuilder
private var compactView: some View { private var compactView: some View {
HStack { HStack {
@ -35,7 +35,7 @@ public struct AppAccountView: View {
} }
} }
} }
private var fullView: some View { private var fullView: some View {
HStack { HStack {
if let account = viewModel.account { if let account = viewModel.account {

View file

@ -20,7 +20,7 @@ struct ConversationsListRow: View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack { HStack {
EmojiTextApp(.init(stringValue: conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", ")), EmojiTextApp(.init(stringValue: conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", ")),
emojis: conversation.accounts.flatMap{ $0.emojis }) emojis: conversation.accounts.flatMap { $0.emojis })
.font(.scaledSubheadline) .font(.scaledSubheadline)
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(theme.labelColor) .foregroundColor(theme.labelColor)

View file

@ -51,7 +51,7 @@ public struct ConversationsListView: View {
} }
} }
} }
if viewModel.nextPage != nil { if viewModel.nextPage != nil {
HStack { HStack {
Spacer() Spacer()

View file

@ -10,7 +10,7 @@ class ConversationsListViewModel: ObservableObject {
@Published var isLoadingNextPage: Bool = false @Published var isLoadingNextPage: Bool = false
@Published var conversations: [Conversation] = [] @Published var conversations: [Conversation] = []
@Published var isError: Bool = false @Published var isError: Bool = false
var nextPage: LinkHandler? var nextPage: LinkHandler?
public init() {} public init() {}
@ -31,7 +31,7 @@ class ConversationsListViewModel: ObservableObject {
isLoadingFirstPage = false isLoadingFirstPage = false
} }
} }
func fetchNextPage() async { func fetchNextPage() async {
if let maxId = nextPage?.maxId, let client { if let maxId = nextPage?.maxId, let client {
do { do {
@ -43,8 +43,7 @@ class ConversationsListViewModel: ObservableObject {
nextPage = nil nextPage = nil
} }
isLoadingNextPage = false isLoadingNextPage = false
} catch { } catch {}
}
} }
} }

View file

@ -1,11 +1,11 @@
import SwiftUI import SwiftUI
public let availableColorsSets: [ColorSetCouple] = public let availableColorsSets: [ColorSetCouple] =
[.init(light: IceCubeLight(), dark: IceCubeDark()), [.init(light: IceCubeLight(), dark: IceCubeDark()),
.init(light: IceCubeNeonLight(), dark: IceCubeNeonDark()), .init(light: IceCubeNeonLight(), dark: IceCubeNeonDark()),
.init(light: DesertLight(), dark: DesertDark()), .init(light: DesertLight(), dark: DesertDark()),
.init(light: NemesisLight(), dark: NemesisDark()), .init(light: NemesisLight(), dark: NemesisDark()),
.init(light: MediumLight(), dark: MediumDark())] .init(light: MediumLight(), dark: MediumDark())]
public protocol ColorSet { public protocol ColorSet {
var name: ColorSetName { get } var name: ColorSetName { get }
@ -37,7 +37,7 @@ public struct ColorSetCouple: Identifiable {
public var id: String { public var id: String {
dark.name.rawValue + light.name.rawValue dark.name.rawValue + light.name.rawValue
} }
public let light: ColorSet public let light: ColorSet
public let dark: ColorSet public let dark: ColorSet
} }

View file

@ -1,71 +1,69 @@
import SwiftUI
import Env import Env
import SwiftUI
@MainActor @MainActor
extension Font { public extension Font {
static func userScaledFontSize(baseSize: CGFloat) -> CGFloat {
public static func userScaledFontSize(baseSize: CGFloat) -> CGFloat {
UIFontMetrics.default.scaledValue(for: baseSize * UserPreferences.shared.fontSizeScale) UIFontMetrics.default.scaledValue(for: baseSize * UserPreferences.shared.fontSizeScale)
} }
public static var scaledTitle: Font { static var scaledTitle: Font {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return .system(size: userScaledFontSize(baseSize: 28)) return .system(size: userScaledFontSize(baseSize: 28))
} else { } else {
return .title return .title
} }
} }
public static var scaledHeadline: Font { static var scaledHeadline: Font {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return .system(size: userScaledFontSize(baseSize: 20), weight: .semibold) return .system(size: userScaledFontSize(baseSize: 20), weight: .semibold)
} else { } else {
return .headline return .headline
} }
} }
public static var scaledBody: Font { static var scaledBody: Font {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return .system(size: userScaledFontSize(baseSize: 19)) return .system(size: userScaledFontSize(baseSize: 19))
} else { } else {
return .body return .body
} }
} }
public static var scaledBodyUIFont: UIFont { static var scaledBodyUIFont: UIFont {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return UIFont.systemFont(ofSize: userScaledFontSize(baseSize: 19)) return UIFont.systemFont(ofSize: userScaledFontSize(baseSize: 19))
} else { } else {
return UIFont.systemFont(ofSize: 17) return UIFont.systemFont(ofSize: 17)
} }
} }
public static var scaledCallout: Font { static var scaledCallout: Font {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return .system(size: userScaledFontSize(baseSize: 17)) return .system(size: userScaledFontSize(baseSize: 17))
} else { } else {
return .callout return .callout
} }
} }
public static var scaledSubheadline: Font { static var scaledSubheadline: Font {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return .system(size: userScaledFontSize(baseSize: 16)) return .system(size: userScaledFontSize(baseSize: 16))
} else { } else {
return .subheadline return .subheadline
} }
} }
static var scaledFootnote: Font {
public static var scaledFootnote: Font {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return .system(size: userScaledFontSize(baseSize: 15)) return .system(size: userScaledFontSize(baseSize: 15))
} else { } else {
return .footnote return .footnote
} }
} }
public static var scaledCaption: Font { static var scaledCaption: Font {
if ProcessInfo.processInfo.isiOSAppOnMac { if ProcessInfo.processInfo.isiOSAppOnMac {
return .system(size: userScaledFontSize(baseSize: 14)) return .system(size: userScaledFontSize(baseSize: 14))
} else { } else {

View file

@ -11,9 +11,9 @@ public extension View {
struct ThemeApplier: ViewModifier { struct ThemeApplier: ViewModifier {
@Environment(\EnvironmentValues.colorScheme) var colorScheme @Environment(\EnvironmentValues.colorScheme) var colorScheme
@ObservedObject var theme: Theme @ObservedObject var theme: Theme
var actualColorScheme: SwiftUI.ColorScheme? { var actualColorScheme: SwiftUI.ColorScheme? {
if theme.followSystemColorScheme { if theme.followSystemColorScheme {
return nil return nil
@ -29,12 +29,13 @@ struct ThemeApplier: ViewModifier {
.onAppear { .onAppear {
// If theme is never set before set the default store. This should only execute once after install. // If theme is never set before set the default store. This should only execute once after install.
if !theme.isThemePreviouslySet { if !theme.isThemePreviouslySet {
theme.selectedSet = colorScheme == .dark ? .iceCubeDark : .iceCubeLight theme.selectedSet = colorScheme == .dark ? .iceCubeDark : .iceCubeLight
theme.isThemePreviouslySet = true theme.isThemePreviouslySet = true
} else if theme.followSystemColorScheme && theme.isThemePreviouslySet, } else if theme.followSystemColorScheme, theme.isThemePreviouslySet,
let sets = availableColorsSets let sets = availableColorsSets
.first(where: { $0.light.name == theme.selectedSet || $0.dark.name == theme.selectedSet }) { .first(where: { $0.light.name == theme.selectedSet || $0.dark.name == theme.selectedSet })
theme.selectedSet = colorScheme == .dark ? sets.dark.name : sets.light.name {
theme.selectedSet = colorScheme == .dark ? sets.dark.name : sets.light.name
} }
setWindowTint(theme.tintColor) setWindowTint(theme.tintColor)
setBarsColor(theme.primaryBackgroundColor) setBarsColor(theme.primaryBackgroundColor)
@ -48,7 +49,8 @@ struct ThemeApplier: ViewModifier {
.onChange(of: colorScheme) { newColorScheme in .onChange(of: colorScheme) { newColorScheme in
if theme.followSystemColorScheme, if theme.followSystemColorScheme,
let sets = availableColorsSets let sets = availableColorsSets
.first(where: { $0.light.name == theme.selectedSet || $0.dark.name == theme.selectedSet }) { .first(where: { $0.light.name == theme.selectedSet || $0.dark.name == theme.selectedSet })
{
theme.selectedSet = newColorScheme == .dark ? sets.dark.name : sets.light.name theme.selectedSet = newColorScheme == .dark ? sets.dark.name : sets.light.name
} }
} }

View file

@ -4,7 +4,7 @@ public struct EmptyView: View {
public let iconName: String public let iconName: String
public let title: LocalizedStringKey public let title: LocalizedStringKey
public let message: LocalizedStringKey public let message: LocalizedStringKey
public init(iconName: String, title: LocalizedStringKey, message: LocalizedStringKey) { public init(iconName: String, title: LocalizedStringKey, message: LocalizedStringKey) {
self.iconName = iconName self.iconName = iconName
self.title = title self.title = title

View file

@ -44,7 +44,7 @@ struct ThemeBoxView: View {
.foregroundColor(color.tintColor) .foregroundColor(color.tintColor)
.font(.system(size: 20)) .font(.system(size: 20))
.fontWeight(.bold) .fontWeight(.bold)
Text("design.theme.toots-preview") Text("design.theme.toots-preview")
.foregroundColor(color.labelColor) .foregroundColor(color.labelColor)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

View file

@ -34,7 +34,7 @@ public enum PollVotingFrequency: String, CaseIterable {
case .oneVote: return false case .oneVote: return false
} }
} }
public var displayString: LocalizedStringKey { public var displayString: LocalizedStringKey {
switch self { switch self {
case .oneVote: return "env.poll-vote-frequency.one" case .oneVote: return "env.poll-vote-frequency.one"

View file

@ -22,9 +22,9 @@ public struct ExploreView: View {
loadingView loadingView
} else if !viewModel.searchQuery.isEmpty { } else if !viewModel.searchQuery.isEmpty {
if viewModel.isSearching { if viewModel.isSearching {
HStack { } HStack {}
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} else if let results = viewModel.results[viewModel.searchQuery], !results.isEmpty { } else if let results = viewModel.results[viewModel.searchQuery], !results.isEmpty {
makeSearchResultsView(results: results) makeSearchResultsView(results: results)
} else { } else {

View file

@ -27,6 +27,7 @@ class ExploreViewModel: ObservableObject {
isSearching = true isSearching = true
} }
} }
@Published var results: [String: SearchResults] = [:] @Published var results: [String: SearchResults] = [:]
@Published var isLoaded = false @Published var isLoaded = false
@Published var isSearching = false @Published var isSearching = false

View file

@ -9,7 +9,7 @@ public struct HTMLString: Decodable, Equatable {
public let asRawText: String public let asRawText: String
public let statusesURLs: [URL] public let statusesURLs: [URL]
public let asSafeMarkdownAttributedString: AttributedString public let asSafeMarkdownAttributedString: AttributedString
public init(from decoder: Decoder) { public init(from decoder: Decoder) {
do { do {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
@ -17,7 +17,7 @@ public struct HTMLString: Decodable, Equatable {
} catch { } catch {
htmlValue = "" htmlValue = ""
} }
do { do {
asMarkdown = try HTMLParser().parse(html: htmlValue) asMarkdown = try HTMLParser().parse(html: htmlValue)
.toMarkdown() .toMarkdown()
@ -25,7 +25,7 @@ public struct HTMLString: Decodable, Equatable {
} catch { } catch {
asMarkdown = htmlValue asMarkdown = htmlValue
} }
var statusesURLs: [URL] = [] var statusesURLs: [URL] = []
do { do {
let document: Document = try SwiftSoup.parse(htmlValue) let document: Document = try SwiftSoup.parse(htmlValue)
@ -42,9 +42,9 @@ public struct HTMLString: Decodable, Equatable {
} catch { } catch {
asRawText = htmlValue asRawText = htmlValue
} }
self.statusesURLs = statusesURLs self.statusesURLs = statusesURLs
do { do {
let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true, let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true,
interpretedSyntax: .inlineOnlyPreservingWhitespace) interpretedSyntax: .inlineOnlyPreservingWhitespace)
@ -53,7 +53,7 @@ public struct HTMLString: Decodable, Equatable {
asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue) asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue)
} }
} }
public init(stringValue: String) { public init(stringValue: String) {
htmlValue = stringValue htmlValue = stringValue
asMarkdown = stringValue asMarkdown = stringValue

View file

@ -22,7 +22,7 @@ extension ServerDate {
dateFormatter.dateStyle = .medium dateFormatter.dateStyle = .medium
return dateFormatter return dateFormatter
}() }()
private static let calendar = Calendar(identifier: .gregorian) private static let calendar = Calendar(identifier: .gregorian)
public var asDate: Date { public var asDate: Date {

View file

@ -9,7 +9,7 @@ public struct SearchResults: Decodable {
public var relationships: [Relationship] = [] public var relationships: [Relationship] = []
public let statuses: [Status] public let statuses: [Status]
public let hashtags: [Tag] public let hashtags: [Tag]
public var isEmpty: Bool { public var isEmpty: Bool {
accounts.isEmpty && statuses.isEmpty && hashtags.isEmpty accounts.isEmpty && statuses.isEmpty && hashtags.isEmpty
} }

View file

@ -4,7 +4,7 @@ public struct StatusHistory: Decodable, Identifiable {
public var id: String { public var id: String {
createdAt.description createdAt.description
} }
public let content: HTMLString public let content: HTMLString
public let createdAt: ServerDate public let createdAt: ServerDate
public let emojis: [Emoji] public let emojis: [Emoji]

View file

@ -2,7 +2,7 @@ import Foundation
public struct DeepLClient { public struct DeepLClient {
private let endpoint = "https://api-free.deepl.com/v2/translate" private let endpoint = "https://api-free.deepl.com/v2/translate"
private var APIKey: String { private var APIKey: String {
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") { if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
let secret = NSDictionary(contentsOfFile: path) let secret = NSDictionary(contentsOfFile: path)
@ -10,28 +10,29 @@ public struct DeepLClient {
} }
return "" return ""
} }
private var authorizationHeaderValue: String { private var authorizationHeaderValue: String {
"DeepL-Auth-Key \(APIKey)" "DeepL-Auth-Key \(APIKey)"
} }
public struct Response: Decodable { public struct Response: Decodable {
public struct Translation: Decodable { public struct Translation: Decodable {
public let detectedSourceLanguage: String public let detectedSourceLanguage: String
public let text: String public let text: String
} }
public let translations: [Translation] public let translations: [Translation]
} }
private var decoder: JSONDecoder { private var decoder: JSONDecoder {
let decoder = JSONDecoder() let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder return decoder
} }
public init() {} public init() {}
public func request(target: String, source: String?, text: String) async throws -> String { public func request(target: String, source _: String?, text: String) async throws -> String {
do { do {
var components = URLComponents(string: endpoint)! var components = URLComponents(string: endpoint)!
var queryItems: [URLQueryItem] = [] var queryItems: [URLQueryItem] = []

View file

@ -75,7 +75,7 @@ public struct OpenAIClient {
public let object: String public let object: String
public let model: String public let model: String
public let choices: [Choice] public let choices: [Choice]
public var trimmedText: String { public var trimmedText: String {
guard var text = choices.first?.text else { guard var text = choices.first?.text else {
return "" return ""

View file

@ -41,7 +41,7 @@ extension Models.Notification.NotificationType {
return "pencil.line" return "pencil.line"
} }
} }
func menuTitle() -> LocalizedStringKey { func menuTitle() -> LocalizedStringKey {
switch self { switch self {
case .status: case .status:

View file

@ -11,7 +11,7 @@ public struct NotificationsListView: View {
@EnvironmentObject private var watcher: StreamWatcher @EnvironmentObject private var watcher: StreamWatcher
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@StateObject private var viewModel = NotificationsViewModel() @StateObject private var viewModel = NotificationsViewModel()
let lockedType: Models.Notification.NotificationType? let lockedType: Models.Notification.NotificationType?
public init(lockedType: Models.Notification.NotificationType?) { public init(lockedType: Models.Notification.NotificationType?) {
@ -22,7 +22,7 @@ public struct NotificationsListView: View {
ScrollView { ScrollView {
LazyVStack { LazyVStack {
notificationsView notificationsView
.frame(maxWidth: .maxColumnWidth) .frame(maxWidth: .maxColumnWidth)
} }
.padding(.top, .layoutPadding + 16) .padding(.top, .layoutPadding + 16)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)

View file

@ -43,8 +43,8 @@ class NotificationsViewModel: ObservableObject {
private var queryTypes: [String]? { private var queryTypes: [String]? {
if let selectedType { if let selectedType {
var excludedTypes = Models.Notification.NotificationType.allCases var excludedTypes = Models.Notification.NotificationType.allCases
excludedTypes.removeAll(where: { $0 == selectedType}) excludedTypes.removeAll(where: { $0 == selectedType })
return excludedTypes.map{ $0.rawValue } return excludedTypes.map { $0.rawValue }
} }
return nil return nil
} }
@ -105,7 +105,7 @@ class NotificationsViewModel: ObservableObject {
} }
func handleEvent(event: any StreamEvent) { func handleEvent(event: any StreamEvent) {
if let event = event as? StreamEventNotification, if let event = event as? StreamEventNotification,
!notifications.contains(where: { $0.id == event.notification.id }) !notifications.contains(where: { $0.id == event.notification.id })
{ {
if let selectedType, event.notification.type == selectedType.rawValue { if let selectedType, event.notification.type == selectedType.rawValue {

View file

@ -16,7 +16,7 @@ class StatusDetailViewModel: ObservableObject {
@Published var state: State = .loading @Published var state: State = .loading
@Published var title: LocalizedStringKey = "" @Published var title: LocalizedStringKey = ""
init(statusId: String) { init(statusId: String) {
state = .loading state = .loading
self.statusId = statusId self.statusId = statusId
@ -59,7 +59,7 @@ class StatusDetailViewModel: ObservableObject {
let status: Status let status: Status
let context: StatusContext let context: StatusContext
} }
private func fetchStatusDetail() async { private func fetchStatusDetail() async {
guard let client, let statusId else { return } guard let client, let statusId else { return }
do { do {
@ -70,7 +70,7 @@ class StatusDetailViewModel: ObservableObject {
state = .error(error: error) state = .error(error: error)
} }
} }
private func fetchContextData(client: Client, statusId: String) async throws -> ContextData { private func fetchContextData(client: Client, statusId: String) async throws -> ContextData {
async let status: Status = client.get(endpoint: Statuses.status(id: statusId)) async let status: Status = client.get(endpoint: Statuses.status(id: statusId))
async let context: StatusContext = client.get(endpoint: Statuses.context(id: statusId)) async let context: StatusContext = client.get(endpoint: Statuses.context(id: statusId))

View file

@ -1,9 +1,9 @@
import DesignSystem import DesignSystem
import Env import Env
import Models import Models
import NukeUI
import PhotosUI import PhotosUI
import SwiftUI import SwiftUI
import NukeUI
struct StatusEditorAccessoryView: View { struct StatusEditorAccessoryView: View {
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@ -53,7 +53,7 @@ struct StatusEditorAccessoryView: View {
Image(systemName: "archivebox") Image(systemName: "archivebox")
} }
} }
if !viewModel.customEmojis.isEmpty { if !viewModel.customEmojis.isEmpty {
Button { Button {
isCustomEmojisSheetDisplay = true isCustomEmojisSheetDisplay = true
@ -167,7 +167,7 @@ struct StatusEditorAccessoryView: View {
} }
.presentationDetents([.medium]) .presentationDetents([.medium])
} }
private var customEmojisSheet: some View { private var customEmojisSheet: some View {
NavigationStack { NavigationStack {
ScrollView { ScrollView {

View file

@ -10,8 +10,8 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
case image = "public.image" case image = "public.image"
case jpeg = "public.jpeg" case jpeg = "public.jpeg"
case png = "public.png" case png = "public.png"
static func types() -> [UTType] { static func types() -> [UTType] {
[.url, .text, .plainText, .image, .jpeg, .png] [.url, .text, .plainText, .image, .jpeg, .png]
} }

View file

@ -79,7 +79,7 @@ public struct StatusEditorView: View {
NotificationCenter.default.post(name: NotificationsName.shareSheetClose, NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
object: nil) object: nil)
} }
Task { Task {
await viewModel.fetchCustomEmojis() await viewModel.fetchCustomEmojis()
} }

View file

@ -53,9 +53,9 @@ public class StatusEditorViewModel: ObservableObject {
@Published var mediasImages: [ImageContainer] = [] @Published var mediasImages: [ImageContainer] = []
@Published var replyToStatus: Status? @Published var replyToStatus: Status?
@Published var embeddedStatus: Status? @Published var embeddedStatus: Status?
@Published var customEmojis: [Emoji] = [] @Published var customEmojis: [Emoji] = []
var canPost: Bool { var canPost: Bool {
statusText.length > 0 || !mediasImages.isEmpty statusText.length > 0 || !mediasImages.isEmpty
} }
@ -126,9 +126,8 @@ public class StatusEditorViewModel: ObservableObject {
} }
} }
// MARK: - Status Text manipulations // MARK: - Status Text manipulations
func insertStatusText(text: String) { func insertStatusText(text: String) {
let string = statusText let string = statusText
string.mutableString.insert(text, at: selectedRange.location) string.mutableString.insert(text, at: selectedRange.location)
@ -195,7 +194,7 @@ public class StatusEditorViewModel: ObservableObject {
} }
} }
} }
private func processText() { private func processText() {
statusText.addAttributes([.foregroundColor: UIColor(Color.label), statusText.addAttributes([.foregroundColor: UIColor(Color.label),
.underlineColor: .clear], .underlineColor: .clear],
@ -245,7 +244,7 @@ public class StatusEditorViewModel: ObservableObject {
range: NSRange(location: range.location, length: range.length)) range: NSRange(location: range.location, length: range.length))
} }
var mediaAdded: Bool = false var mediaAdded = false
statusText.enumerateAttribute(.attachment, in: range) { attachment, range, _ in statusText.enumerateAttribute(.attachment, in: range) { attachment, range, _ in
if let attachment = attachment as? NSTextAttachment, let image = attachment.image { if let attachment = attachment as? NSTextAttachment, let image = attachment.image {
mediasImages.append(.init(image: image, mediaAttachment: nil, error: nil)) mediasImages.append(.init(image: image, mediaAttachment: nil, error: nil))
@ -254,7 +253,7 @@ public class StatusEditorViewModel: ObservableObject {
mediaAdded = true mediaAdded = true
} }
} }
if mediaAdded { if mediaAdded {
processMediasToUpload() processMediasToUpload()
} }
@ -262,6 +261,7 @@ public class StatusEditorViewModel: ObservableObject {
} }
// MARK: - Shar sheet / Item provider // MARK: - Shar sheet / Item provider
private func processItemsProvider(items: [NSItemProvider]) { private func processItemsProvider(items: [NSItemProvider]) {
Task { Task {
var initialText: String = "" var initialText: String = ""
@ -290,21 +290,20 @@ public class StatusEditorViewModel: ObservableObject {
} }
// MARK: - Polls // MARK: - Polls
func resetPollDefaults() { func resetPollDefaults() {
pollOptions = ["", ""] pollOptions = ["", ""]
pollDuration = .oneDay pollDuration = .oneDay
pollVotingFrequency = .oneVote pollVotingFrequency = .oneVote
} }
private func getPollOptionsForAPI() -> [String]? { private func getPollOptionsForAPI() -> [String]? {
let options = pollOptions.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty } let options = pollOptions.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
return options.isEmpty ? nil : options return options.isEmpty ? nil : options
} }
// MARK: - Embeds // MARK: - Embeds
private func checkEmbed() { private func checkEmbed() {
if let url = embeddedStatusURL, if let url = embeddedStatusURL,
!statusText.string.contains(url.absoluteString) !statusText.string.contains(url.absoluteString)
@ -447,7 +446,7 @@ public class StatusEditorViewModel: ObservableObject {
if let index = indexOf(container: container) { if let index = indexOf(container: container) {
do { do {
let media: MediaAttachment = try await client.put(endpoint: Media.media(id: attachment.id, let media: MediaAttachment = try await client.put(endpoint: Media.media(id: attachment.id,
description: description)) description: description))
mediasImages[index] = .init(image: nil, mediaAttachment: media, error: nil) mediasImages[index] = .init(image: nil, mediaAttachment: media, error: nil)
} catch {} } catch {}
} }
@ -462,13 +461,14 @@ public class StatusEditorViewModel: ObservableObject {
filename: "file", filename: "file",
data: data) data: data)
} }
// MARK: - Custom emojis // MARK: - Custom emojis
func fetchCustomEmojis() async { func fetchCustomEmojis() async {
guard let client else { return } guard let client else { return }
do { do {
customEmojis = try await client.get(endpoint: CustomEmojis.customEmojis) ?? [] customEmojis = try await client.get(endpoint: CustomEmojis.customEmojis) ?? []
} catch { } } catch {}
} }
} }

View file

@ -37,7 +37,7 @@ public extension StatusEditorViewModel {
return nil return nil
} }
} }
var title: LocalizedStringKey { var title: LocalizedStringKey {
switch self { switch self {
case .new, .mention, .shareExtension: case .new, .mention, .shareExtension:

View file

@ -1,35 +1,35 @@
import SwiftUI import DesignSystem
import Models import Models
import Network import Network
import DesignSystem import SwiftUI
public struct StatusEditHistoryView: View { public struct StatusEditHistoryView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
private let statusId: String private let statusId: String
@State private var history: [StatusHistory]? @State private var history: [StatusHistory]?
public init(statusId: String) { public init(statusId: String) {
self.statusId = statusId self.statusId = statusId
} }
public var body: some View { public var body: some View {
NavigationStack { NavigationStack {
List { List {
Section { Section {
if let history { if let history {
ForEach(history) { edit in ForEach(history) { edit in
VStack(alignment: .leading, spacing: 8){ VStack(alignment: .leading, spacing: 8) {
EmojiTextApp(edit.content, emojis: edit.emojis) EmojiTextApp(edit.content, emojis: edit.emojis)
.font(.scaledBody) .font(.scaledBody)
Group { Group {
Text(edit.createdAt.asDate, style: .date) + Text(edit.createdAt.asDate, style: .date) +
Text("status.summary.at-time") + Text("status.summary.at-time") +
Text(edit.createdAt.asDate, style: .time) Text(edit.createdAt.asDate, style: .time)
} }
.font(.footnote) .font(.footnote)
.foregroundColor(.gray) .foregroundColor(.gray)

View file

@ -20,11 +20,11 @@ class VideoPlayerViewModel: ObservableObject {
self?.player?.play() self?.player?.play()
} }
} }
func pause() { func pause() {
player?.pause() player?.pause()
} }
func play() { func play() {
player?.play() player?.play()
} }
@ -37,7 +37,7 @@ class VideoPlayerViewModel: ObservableObject {
struct VideoPlayerView: View { struct VideoPlayerView: View {
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@StateObject var viewModel: VideoPlayerViewModel @StateObject var viewModel: VideoPlayerViewModel
var body: some View { var body: some View {
VStack { VStack {
VideoPlayer(player: viewModel.player) VideoPlayer(player: viewModel.player)

View file

@ -10,7 +10,7 @@ public struct StatusPollView: View {
@EnvironmentObject private var currentInstance: CurrentInstance @EnvironmentObject private var currentInstance: CurrentInstance
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount
@StateObject private var viewModel: StatusPollViewModel @StateObject private var viewModel: StatusPollViewModel
private var status: AnyStatus private var status: AnyStatus
public init(poll: Poll, status: AnyStatus) { public init(poll: Poll, status: AnyStatus) {

View file

@ -101,9 +101,9 @@ struct StatusActionsView: View {
Divider() Divider()
HStack { HStack {
Text(viewModel.status.createdAt.asDate, style: .date) + Text(viewModel.status.createdAt.asDate, style: .date) +
Text("status.summary.at-time") + Text("status.summary.at-time") +
Text(viewModel.status.createdAt.asDate, style: .time) + Text(viewModel.status.createdAt.asDate, style: .time) +
Text(" ·") Text(" ·")
Image(systemName: viewModel.status.visibility.iconName) Image(systemName: viewModel.status.visibility.iconName)
Spacer() Spacer()
Text(viewModel.status.application?.name ?? "") Text(viewModel.status.application?.name ?? "")
@ -116,14 +116,14 @@ struct StatusActionsView: View {
} }
.font(.scaledCaption) .font(.scaledCaption)
.foregroundColor(.gray) .foregroundColor(.gray)
if let editedAt = viewModel.status.editedAt { if let editedAt = viewModel.status.editedAt {
Divider() Divider()
HStack { HStack {
Text("status.summary.edited-time") + Text("status.summary.edited-time") +
Text(editedAt.asDate, style: .date) + Text(editedAt.asDate, style: .date) +
Text("status.summary.at-time") + Text("status.summary.at-time") +
Text(editedAt.asDate, style: .time) Text(editedAt.asDate, style: .time)
Spacer() Spacer()
} }
.onTapGesture { .onTapGesture {

View file

@ -1,14 +1,14 @@
import DesignSystem import DesignSystem
import Env import Env
import Models import Models
import Nuke
import NukeUI import NukeUI
import Shimmer import Shimmer
import SwiftUI import SwiftUI
import Nuke
public struct StatusMediaPreviewView: View { public struct StatusMediaPreviewView: View {
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var quickLook: QuickLook @EnvironmentObject private var quickLook: QuickLook
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -146,7 +146,7 @@ public struct StatusMediaPreviewView: View {
let availableWidth = UIScreen.main.bounds.width - (.layoutPadding * 2) - avatarColumnWidth let availableWidth = UIScreen.main.bounds.width - (.layoutPadding * 2) - avatarColumnWidth
let newSize = imageSize(from: size, let newSize = imageSize(from: size,
newWidth: availableWidth) newWidth: availableWidth)
LazyImage(url: attachment.url) { state in LazyImage(url: attachment.url) { state in
if let image = state.image { if let image = state.image {
image image
@ -175,7 +175,6 @@ public struct StatusMediaPreviewView: View {
.fill(Color.gray) .fill(Color.gray)
.frame(maxHeight: isNotifications || theme.statusDisplayStyle == .compact ? imageMaxHeight : nil) .frame(maxHeight: isNotifications || theme.statusDisplayStyle == .compact ? imageMaxHeight : nil)
.shimmering() .shimmering()
} }
) )
} }
@ -197,7 +196,6 @@ public struct StatusMediaPreviewView: View {
isAltAlertDisplayed = true isAltAlertDisplayed = true
} label: { } label: {
Text("ALT") Text("ALT")
} }
.padding(8) .padding(8)
.background(.thinMaterial) .background(.thinMaterial)
@ -298,7 +296,6 @@ public struct StatusMediaPreviewView: View {
Label("status.media.sensitive.show", systemImage: "eye") Label("status.media.sensitive.show", systemImage: "eye")
} else { } else {
Label("status.media.content.show", systemImage: "eye") Label("status.media.content.show", systemImage: "eye")
} }
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
@ -307,21 +304,21 @@ public struct StatusMediaPreviewView: View {
} }
private var cornerSensitiveButton: some View { private var cornerSensitiveButton: some View {
HStack{ HStack {
Button { Button {
withAnimation { withAnimation {
isHidingMedia = true isHidingMedia = true
} }
} label: { } label: {
Image(systemName: "eye.slash") Image(systemName: "eye.slash")
.frame(minHeight:21) // Match the alt button in case it is also present .frame(minHeight: 21) // Match the alt button in case it is also present
} }
.padding(10) .padding(10)
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
Spacer() Spacer()
} }
} }
@ViewBuilder @ViewBuilder
private func contextMenuForMedia(mediaAttachement: MediaAttachment) -> some View { private func contextMenuForMedia(mediaAttachement: MediaAttachment) -> some View {
if let url = mediaAttachement.url { if let url = mediaAttachement.url {
@ -337,7 +334,7 @@ public struct StatusMediaPreviewView: View {
do { do {
let image = try await ImagePipeline.shared.image(for: url).image let image = try await ImagePipeline.shared.image(for: url).image
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
} catch { } } catch {}
} }
} label: { } label: {
Label("status.media.contextmenu.save", systemImage: "square.and.arrow.down") Label("status.media.contextmenu.save", systemImage: "square.and.arrow.down")
@ -347,7 +344,7 @@ public struct StatusMediaPreviewView: View {
do { do {
let image = try await ImagePipeline.shared.image(for: url).image let image = try await ImagePipeline.shared.image(for: url).image
UIPasteboard.general.image = image UIPasteboard.general.image = image
} catch { } } catch {}
} }
} label: { } label: {
Label("status.media.contextmenu.copy", systemImage: "doc.on.doc") Label("status.media.contextmenu.copy", systemImage: "doc.on.doc")

View file

@ -67,31 +67,31 @@ public struct StatusRowView: View {
viewModel.displaySpoiler = false viewModel.displaySpoiler = false
} }
} }
.contextMenu { .contextMenu {
StatusRowContextMenu(viewModel: viewModel) StatusRowContextMenu(viewModel: viewModel)
} }
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine) .accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
.accessibilityActions { .accessibilityActions {
// Add the individual mentions as accessibility actions // Add the individual mentions as accessibility actions
ForEach(viewModel.status.mentions, id: \.id) { mention in ForEach(viewModel.status.mentions, id: \.id) { mention in
Button("@\(mention.username)") { Button("@\(mention.username)") {
routerPath.navigate(to: .accountDetail(id: mention.id)) routerPath.navigate(to: .accountDetail(id: mention.id))
} }
} }
Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") { Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") {
withAnimation { withAnimation {
viewModel.displaySpoiler.toggle() viewModel.displaySpoiler.toggle()
} }
} }
Button("@\(viewModel.status.account.username)") { Button("@\(viewModel.status.account.username)") {
routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id)) routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
} }
StatusRowContextMenu(viewModel: viewModel) StatusRowContextMenu(viewModel: viewModel)
} }
.background { .background {
Color.clear Color.clear
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture { .onTapGesture {
@ -125,15 +125,15 @@ public struct StatusRowView: View {
Text("status.row.was-boosted") Text("status.row.was-boosted")
} else { } else {
Text("status.row.you-boosted") Text("status.row.you-boosted")
} }
} }
.accessibilityElement() .accessibilityElement()
.accessibilityLabel( .accessibilityLabel(
Text("\(viewModel.status.account.safeDisplayName)") Text("\(viewModel.status.account.safeDisplayName)")
+ Text(" ") + Text(" ")
+ Text(viewModel.status.account.username != account.account?.username ? "status.row.was-boosted" : "status.row.you-boosted") + Text(viewModel.status.account.username != account.account?.username ? "status.row.was-boosted" : "status.row.you-boosted")
) )
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundColor(.gray) .foregroundColor(.gray)
.fontWeight(.semibold) .fontWeight(.semibold)
.onTapGesture { .onTapGesture {
@ -192,22 +192,22 @@ public struct StatusRowView: View {
.buttonStyle(.plain) .buttonStyle(.plain)
Spacer() Spacer()
menuButton menuButton
.accessibilityHidden(true) .accessibilityHidden(true)
} }
.accessibilityElement() .accessibilityElement()
.accessibilityLabel(Text("\(status.account.displayName), \(status.createdAt.formatted)")) .accessibilityLabel(Text("\(status.account.displayName), \(status.createdAt.formatted)"))
} }
makeStatusContentView(status: status) makeStatusContentView(status: status)
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture { .onTapGesture {
viewModel.navigateToDetail(routerPath: routerPath) viewModel.navigateToDetail(routerPath: routerPath)
} }
} }
} }
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine) .accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
.accessibilityAction { .accessibilityAction {
viewModel.navigateToDetail(routerPath: routerPath) viewModel.navigateToDetail(routerPath: routerPath)
} }
} }
private func makeStatusContentView(status: AnyStatus) -> some View { private func makeStatusContentView(status: AnyStatus) -> some View {
@ -225,7 +225,7 @@ public struct StatusRowView: View {
.buttonStyle(.bordered) .buttonStyle(.bordered)
.accessibilityHidden(true) .accessibilityHidden(true)
} }
if !viewModel.displaySpoiler { if !viewModel.displaySpoiler {
HStack { HStack {
EmojiTextApp(status.content, emojis: status.emojis) EmojiTextApp(status.content, emojis: status.emojis)
@ -235,7 +235,7 @@ public struct StatusRowView: View {
}) })
Spacer() Spacer()
} }
makeTranslateView(status: status) makeTranslateView(status: status)
if let poll = status.poll { if let poll = status.poll {
@ -243,7 +243,7 @@ public struct StatusRowView: View {
} }
makeMediasView(status: status) makeMediasView(status: status)
.accessibilityHidden(!viewModel.isFocused) .accessibilityHidden(!viewModel.isFocused)
makeCardView(status: status) makeCardView(status: status)
} }
} }
@ -282,14 +282,15 @@ public struct StatusRowView: View {
.foregroundColor(.gray) .foregroundColor(.gray)
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
@ViewBuilder @ViewBuilder
private func makeTranslateView(status: AnyStatus) -> some View { private func makeTranslateView(status: AnyStatus) -> some View {
if let userLang = preferences.serverPreferences?.postLanguage, if let userLang = preferences.serverPreferences?.postLanguage,
status.language != nil, status.language != nil,
userLang != status.language, userLang != status.language,
!status.content.asRawText.isEmpty, !status.content.asRawText.isEmpty,
viewModel.translation == nil { viewModel.translation == nil
{
Button { Button {
Task { Task {
await viewModel.translate(userLang: userLang) await viewModel.translate(userLang: userLang)
@ -314,7 +315,7 @@ public struct StatusRowView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
} }
@ViewBuilder @ViewBuilder
private func makeMediasView(status: AnyStatus) -> some View { private func makeMediasView(status: AnyStatus) -> some View {
if !status.mediaAttachments.isEmpty { if !status.mediaAttachments.isEmpty {
@ -334,7 +335,7 @@ public struct StatusRowView: View {
} }
} }
} }
@ViewBuilder @ViewBuilder
private func makeCardView(status: AnyStatus) -> some View { private func makeCardView(status: AnyStatus) -> some View {
if let card = status.card, if let card = status.card,

View file

@ -22,7 +22,7 @@ public class StatusRowViewModel: ObservableObject {
@Published var displaySpoiler: Bool = false @Published var displaySpoiler: Bool = false
@Published var isEmbedLoading: Bool = true @Published var isEmbedLoading: Bool = true
@Published var isFiltered: Bool = false @Published var isFiltered: Bool = false
@Published var translation: String? @Published var translation: String?
@Published var isLoadingTranslation: Bool = false @Published var isLoadingTranslation: Bool = false
@ -223,7 +223,7 @@ public class StatusRowViewModel: ObservableObject {
reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
repliesCount = status.reblog?.repliesCount ?? status.repliesCount repliesCount = status.reblog?.repliesCount ?? status.repliesCount
} }
func translate(userLang: String) async { func translate(userLang: String) async {
let client = DeepLClient() let client = DeepLClient()
do { do {

View file

@ -38,7 +38,7 @@ public enum TimelineFilter: Hashable, Equatable {
return server return server
} }
} }
public func localizedTitle() -> LocalizedStringKey { public func localizedTitle() -> LocalizedStringKey {
switch self { switch self {
case .federated: case .federated:
@ -57,7 +57,7 @@ public enum TimelineFilter: Hashable, Equatable {
return LocalizedStringKey(server) return LocalizedStringKey(server)
} }
} }
public func iconName() -> String? { public func iconName() -> String? {
switch self { switch self {
case .federated: case .federated: