Swiftformat

This commit is contained in:
Thomas Ricouard 2023-02-21 07:23:42 +01:00
parent 94d50fafc4
commit b259b6739e
31 changed files with 164 additions and 165 deletions

View file

@ -3,12 +3,12 @@ import AppAccount
import Conversations import Conversations
import DesignSystem import DesignSystem
import Env import Env
import LinkPresentation
import Lists import Lists
import Models
import Status import Status
import SwiftUI import SwiftUI
import Timeline import Timeline
import LinkPresentation
import Models
@MainActor @MainActor
extension View { extension View {
@ -46,7 +46,7 @@ extension View {
} }
} }
} }
func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View { func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View {
sheet(item: sheetDestinations) { destination in sheet(item: sheetDestinations) { destination in
switch destination { switch destination {
@ -99,7 +99,7 @@ extension View {
} }
} }
} }
func withEnvironments() -> some View { func withEnvironments() -> some View {
environmentObject(CurrentAccount.shared) environmentObject(CurrentAccount.shared)
.environmentObject(UserPreferences.shared) .environmentObject(UserPreferences.shared)
@ -114,38 +114,39 @@ extension View {
struct ActivityView: UIViewControllerRepresentable { struct ActivityView: UIViewControllerRepresentable {
let image: UIImage let image: UIImage
let status: Status let status: Status
class LinkDelegate: NSObject, UIActivityItemSource { class LinkDelegate: NSObject, UIActivityItemSource {
let image: UIImage let image: UIImage
let status: Status let status: Status
init(image: UIImage, status: Status) { init(image: UIImage, status: Status) {
self.image = image self.image = image
self.status = status self.status = status
} }
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? { func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? {
let imageProvider = NSItemProvider(object: image) let imageProvider = NSItemProvider(object: image)
let metadata = LPLinkMetadata() let metadata = LPLinkMetadata()
metadata.imageProvider = imageProvider metadata.imageProvider = imageProvider
metadata.title = status.reblog?.content.asRawText ?? status.content.asRawText metadata.title = status.reblog?.content.asRawText ?? status.content.asRawText
return metadata return metadata
} }
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any {
image image
} }
func activityViewController(_ activityViewController: UIActivityViewController, func activityViewController(_: UIActivityViewController,
itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { itemForActivityType _: UIActivity.ActivityType?) -> Any?
{
nil nil
} }
} }
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController { func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
return UIActivityViewController(activityItems: [image, LinkDelegate(image: image, status: status)], return UIActivityViewController(activityItems: [image, LinkDelegate(image: image, status: status)],
applicationActivities: nil) applicationActivities: nil)
} }
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {} func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
} }

View file

@ -100,7 +100,8 @@ struct IceCubesApp: App {
private func badgeFor(tab: Tab) -> Int { private func badgeFor(tab: Tab) -> Int {
if tab == .notifications && selectedTab != tab, if tab == .notifications && selectedTab != tab,
let token = appAccountsManager.currentAccount.oauthToken { let token = appAccountsManager.currentAccount.oauthToken
{
return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token) return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token)
} }
return 0 return 0
@ -169,7 +170,8 @@ struct IceCubesApp: App {
} }
selectedTab = newTab selectedTab = newTab
if selectedTab == .notifications, if selectedTab == .notifications,
let token = appAccountsManager.currentAccount.oauthToken { let token = appAccountsManager.currentAccount.oauthToken
{
userPreferences.setNotification(count: 0, token: token) userPreferences.setNotification(count: 0, token: token)
watcher.unreadNotificationsCount = 0 watcher.unreadNotificationsCount = 0
} }

View file

@ -15,7 +15,7 @@ private struct SafariRouter: ViewModifier {
@EnvironmentObject private var routerPath: RouterPath @EnvironmentObject private var routerPath: RouterPath
@StateObject private var safariManager = InAppSafariManager() @StateObject private var safariManager = InAppSafariManager()
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.environment(\.openURL, OpenURLAction { url in .environment(\.openURL, OpenURLAction { url in
@ -58,48 +58,48 @@ private struct SafariRouter: ViewModifier {
private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewControllerDelegate { private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewControllerDelegate {
var windowScene: UIWindowScene? var windowScene: UIWindowScene?
let viewController: UIViewController = UIViewController() let viewController: UIViewController = .init()
var window: UIWindow? var window: UIWindow?
@MainActor @MainActor
func open(_ url: URL) -> OpenURLAction.Result { func open(_ url: URL) -> OpenURLAction.Result {
guard let windowScene = windowScene else { return .systemAction } guard let windowScene = windowScene else { return .systemAction }
self.window = setupWindow(windowScene: windowScene) window = setupWindow(windowScene: windowScene)
let configuration = SFSafariViewController.Configuration() let configuration = SFSafariViewController.Configuration()
configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView
let safari = SFSafariViewController(url: url, configuration: configuration) let safari = SFSafariViewController(url: url, configuration: configuration)
safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor) safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor)
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor) safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
safari.delegate = self safari.delegate = self
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.viewController.present(safari, animated: true) self?.viewController.present(safari, animated: true)
} }
return .handled return .handled
} }
func setupWindow(windowScene: UIWindowScene) -> UIWindow { func setupWindow(windowScene: UIWindowScene) -> UIWindow {
let window = self.window ?? UIWindow(windowScene: windowScene) let window = self.window ?? UIWindow(windowScene: windowScene)
window.rootViewController = viewController window.rootViewController = viewController
window.makeKeyAndVisible() window.makeKeyAndVisible()
switch Theme.shared.selectedScheme { switch Theme.shared.selectedScheme {
case .dark: case .dark:
window.overrideUserInterfaceStyle = .dark window.overrideUserInterfaceStyle = .dark
case .light: case .light:
window.overrideUserInterfaceStyle = .light window.overrideUserInterfaceStyle = .light
} }
self.window = window self.window = window
return window return window
} }
func safariViewControllerDidFinish(_ controller: SFSafariViewController) { func safariViewControllerDidFinish(_: SFSafariViewController) {
window?.resignKey() window?.resignKey()
window?.isHidden = false window?.isHidden = false
window = nil window = nil
@ -109,31 +109,30 @@ private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewContro
private struct WindowReader: UIViewRepresentable { private struct WindowReader: UIViewRepresentable {
var onUpdate: (UIWindow) -> Void var onUpdate: (UIWindow) -> Void
func makeUIView(context: Context) -> InjectView { func makeUIView(context _: Context) -> InjectView {
InjectView(onUpdate: onUpdate) InjectView(onUpdate: onUpdate)
} }
func updateUIView(_ uiView: InjectView, context: Context) { func updateUIView(_: InjectView, context _: Context) {}
}
class InjectView: UIView { class InjectView: UIView {
var onUpdate: (UIWindow) -> Void var onUpdate: (UIWindow) -> Void
init(onUpdate: @escaping (UIWindow) -> Void) { init(onUpdate: @escaping (UIWindow) -> Void) {
self.onUpdate = onUpdate self.onUpdate = onUpdate
super.init(frame: .zero) super.init(frame: .zero)
isHidden = true isHidden = true
isUserInteractionEnabled = false isUserInteractionEnabled = false
} }
@available(*, unavailable) @available(*, unavailable)
required init?(coder: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func willMove(toWindow newWindow: UIWindow?) { override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow) super.willMove(toWindow: newWindow)
if let window = newWindow { if let window = newWindow {
onUpdate(window) onUpdate(window)
} else { } else {

View file

@ -20,12 +20,13 @@ struct SideBarView<Content: View>: View {
private func badgeFor(tab: Tab) -> Int { private func badgeFor(tab: Tab) -> Int {
if tab == .notifications && selectedTab != tab, if tab == .notifications && selectedTab != tab,
let token = appAccounts.currentAccount.oauthToken { let token = appAccounts.currentAccount.oauthToken
{
return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token) return watcher.unreadNotificationsCount + userPreferences.getNotificationsCount(for: token)
} }
return 0 return 0
} }
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,
@ -37,7 +38,7 @@ struct SideBarView<Content: View>: View {
.contentShape(Rectangle()) .contentShape(Rectangle())
.frame(width: .sidebarWidth, height: 50) .frame(width: .sidebarWidth, height: 50)
} }
private func makeBadgeView(count: Int) -> some View { private func makeBadgeView(count: Int) -> some View {
ZStack { ZStack {
Circle() Circle()
@ -78,8 +79,9 @@ struct SideBarView<Content: View>: View {
ZStack(alignment: .topTrailing) { ZStack(alignment: .topTrailing) {
AppAccountView(viewModel: .init(appAccount: account, isCompact: true)) AppAccountView(viewModel: .init(appAccount: account, isCompact: true))
if showBadge, if showBadge,
let token = account.oauthToken, let token = account.oauthToken,
userPreferences.getNotificationsCount(for: token) > 0 { userPreferences.getNotificationsCount(for: token) > 0
{
makeBadgeView(count: userPreferences.getNotificationsCount(for: token)) makeBadgeView(count: userPreferences.getNotificationsCount(for: token))
} }
} }

View file

@ -60,7 +60,7 @@ struct DisplaySettingsView: View {
theme.chosenFont = UIFont(name: "OpenDyslexic", size: 1) theme.chosenFont = UIFont(name: "OpenDyslexic", size: 1)
case .hyperLegible: case .hyperLegible:
theme.chosenFont = UIFont(name: "Atkinson Hyperlegible", size: 1) theme.chosenFont = UIFont(name: "Atkinson Hyperlegible", size: 1)
case.SFRounded: case .SFRounded:
theme.chosenFont = UIFont.systemFont(ofSize: 1).rounded() theme.chosenFont = UIFont.systemFont(ofSize: 1).rounded()
case .custom: case .custom:
isFontSelectorPresented = true isFontSelectorPresented = true

View file

@ -64,7 +64,7 @@ class NotificationService: UNNotificationServiceExtension {
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf")) bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf"))
let preferences = UserPreferences.shared let preferences = UserPreferences.shared
if let token = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == notification.accessToken})?.oauthToken { if let token = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == notification.accessToken })?.oauthToken {
var currentCount = preferences.getNotificationsCount(for: token) var currentCount = preferences.getNotificationsCount(for: token)
currentCount += 1 currentCount += 1
preferences.setNotification(count: currentCount, token: token) preferences.setNotification(count: currentCount, token: token)

View file

@ -150,9 +150,10 @@ struct AccountDetailHeaderView: View {
} }
} }
} }
if let note = viewModel.relationship?.note, !note.isEmpty, if let note = viewModel.relationship?.note, !note.isEmpty,
!viewModel.isCurrentUser { !viewModel.isCurrentUser
{
makeNoteView(note) makeNoteView(note)
} }
@ -162,7 +163,7 @@ struct AccountDetailHeaderView: View {
.environment(\.openURL, OpenURLAction { url in .environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url) routerPath.handle(url: url)
}) })
fieldsView fieldsView
} }
.padding(.horizontal, .layoutPadding) .padding(.horizontal, .layoutPadding)
@ -204,7 +205,7 @@ struct AccountDetailHeaderView: View {
.padding(.top, 6) .padding(.top, 6)
} }
} }
@ViewBuilder @ViewBuilder
private func makeNoteView(_ note: String) -> some View { private func makeNoteView(_ note: String) -> some View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
@ -221,7 +222,7 @@ struct AccountDetailHeaderView: View {
) )
} }
} }
@ViewBuilder @ViewBuilder
private var fieldsView: some View { private var fieldsView: some View {
if !viewModel.fields.isEmpty { if !viewModel.fields.isEmpty {

View file

@ -485,8 +485,7 @@ public struct AccountDetailView: View {
private extension View { private extension View {
func applyAccountDetailsRowStyle(theme: Theme) -> some View { func applyAccountDetailsRowStyle(theme: Theme) -> some View {
self listRowInsets(.init())
.listRowInsets(.init())
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }

View file

@ -6,12 +6,12 @@ public struct EditRelationshipNoteView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
// need this model to refresh after storing the new note on mastodon // need this model to refresh after storing the new note on mastodon
var accountDetailViewModel: AccountDetailViewModel var accountDetailViewModel: AccountDetailViewModel
@StateObject private var viewModel = EditRelationshipNoteViewModel() @StateObject private var viewModel = EditRelationshipNoteViewModel()
public var body: some View { public var body: some View {
NavigationStack { NavigationStack {
Form { Form {
@ -31,8 +31,8 @@ public struct EditRelationshipNoteView: View {
.alert("account.relation.note.edit.error.save.title", .alert("account.relation.note.edit.error.save.title",
isPresented: $viewModel.saveError, isPresented: $viewModel.saveError,
actions: { actions: {
Button("alert.button.ok", action: {}) Button("alert.button.ok", action: {})
}, message: { Text("account.relation.note.edit.error.save.message") }) }, message: { Text("account.relation.note.edit.error.save.message") })
.task { .task {
viewModel.client = client viewModel.client = client
viewModel.relatedAccountId = accountDetailViewModel.accountId viewModel.relatedAccountId = accountDetailViewModel.accountId
@ -40,7 +40,7 @@ public struct EditRelationshipNoteView: View {
} }
} }
} }
@ToolbarContentBuilder @ToolbarContentBuilder
private var toolbarContent: some ToolbarContent { private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
@ -48,7 +48,7 @@ public struct EditRelationshipNoteView: View {
dismiss() dismiss()
} }
} }
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {
Task { Task {

View file

@ -6,15 +6,16 @@ class EditRelationshipNoteViewModel: ObservableObject {
public var note: String = "" public var note: String = ""
public var relatedAccountId: String? public var relatedAccountId: String?
public var client: Client? public var client: Client?
@Published var isSaving: Bool = false @Published var isSaving: Bool = false
@Published var saveError: Bool = false @Published var saveError: Bool = false
init() {} init() {}
func save() async { func save() async {
if relatedAccountId != nil, if relatedAccountId != nil,
client != nil { client != nil
{
isSaving = true isSaving = true
do { do {
let _ = try await client!.post(endpoint: Accounts.relationshipNote(id: relatedAccountId!, json: RelationshipNoteData(note: note))) let _ = try await client!.post(endpoint: Accounts.relationshipNote(id: relatedAccountId!, json: RelationshipNoteData(note: note)))

View file

@ -92,7 +92,8 @@ public struct AppAccountsSelectorView: View {
Image(uiImage: image) Image(uiImage: image)
} }
if let token = viewModel.appAccount.oauthToken, if let token = viewModel.appAccount.oauthToken,
preferences.getNotificationsCount(for: token) > 0 { preferences.getNotificationsCount(for: token) > 0
{
Text("\(viewModel.account?.displayName ?? "") (\(preferences.getNotificationsCount(for: token)))") Text("\(viewModel.account?.displayName ?? "") (\(preferences.getNotificationsCount(for: token)))")
} else { } else {
Text("\(viewModel.account?.displayName ?? "")") Text("\(viewModel.account?.displayName ?? "")")

View file

@ -15,12 +15,10 @@ public extension Font {
private static let onMac = ProcessInfo.processInfo.isiOSAppOnMac private static let onMac = ProcessInfo.processInfo.isiOSAppOnMac
private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font { private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font {
if let chosenFont = Theme.shared.chosenFont { if let chosenFont = Theme.shared.chosenFont {
if chosenFont.fontName == ".AppleSystemUIFontRounded-Regular" { if chosenFont.fontName == ".AppleSystemUIFontRounded-Regular" {
return .system(size: size, design: .rounded) return .system(size: size, design: .rounded)
} } else {
else {
return .custom(chosenFont.fontName, size: size, relativeTo: textStyle) return .custom(chosenFont.fontName, size: size, relativeTo: textStyle)
} }
} }
@ -72,13 +70,11 @@ public extension Font {
} }
} }
public extension UIFont {
func rounded() -> UIFont {
extension UIFont { guard let descriptor = fontDescriptor.withDesign(.rounded) else {
public func rounded() -> UIFont { return self
guard let descriptor = fontDescriptor.withDesign(.rounded) else {
return self
}
return UIFont(descriptor: descriptor, size: pointSize)
} }
return UIFont(descriptor: descriptor, size: pointSize)
}
} }

View file

@ -27,7 +27,7 @@ public class Theme: ObservableObject {
case .hyperLegible: case .hyperLegible:
return "Hyper Legible" return "Hyper Legible"
case .SFRounded: case .SFRounded:
return "SF Rounded" return "SF Rounded"
case .custom: case .custom:
return "settings.display.font.custom" return "settings.display.font.custom"
} }
@ -87,7 +87,7 @@ public class Theme: ObservableObject {
} }
} }
} }
public var chosenFont: UIFont? { public var chosenFont: UIFont? {
get { get {
guard let chosenFontData, guard let chosenFontData,

View file

@ -1,5 +1,5 @@
import NukeUI
import Nuke import Nuke
import NukeUI
import Shimmer import Shimmer
import SwiftUI import SwiftUI
@ -97,17 +97,17 @@ public struct AvatarView: View {
} }
private struct AvatarPlaceholderView: View { private struct AvatarPlaceholderView: View {
let size: AvatarView.Size let size: AvatarView.Size
var body: some View { var body: some View {
if size == .badge { if size == .badge {
Circle() Circle()
.fill(.gray) .fill(.gray)
.frame(width: size.size.width, height: size.size.height) .frame(width: size.size.width, height: size.size.height)
} else { } else {
RoundedRectangle(cornerRadius: size.cornerRadius) RoundedRectangle(cornerRadius: size.cornerRadius)
.fill(.gray) .fill(.gray)
.frame(width: size.size.width, height: size.size.height) .frame(width: size.size.width, height: size.size.height)
}
} }
}
} }

View file

@ -32,7 +32,7 @@ public extension EnvironmentValues {
get { self[IsCompact.self] } get { self[IsCompact.self] }
set { self[IsCompact.self] = newValue } set { self[IsCompact.self] = newValue }
} }
var isInCaptureMode: Bool { var isInCaptureMode: Bool {
get { self[IsInCaptureMode.self] } get { self[IsInCaptureMode.self] }
set { self[IsInCaptureMode.self] = newValue } set { self[IsInCaptureMode.self] = newValue }

View file

@ -96,13 +96,13 @@ public class UserPreferences: ObservableObject {
public func setNotification(count: Int, token: OauthToken) { public func setNotification(count: Int, token: OauthToken) {
Self.sharedDefault?.set(count, forKey: "push_notifications_count_\(token.createdAt)") Self.sharedDefault?.set(count, forKey: "push_notifications_count_\(token.createdAt)")
} }
public func getNotificationsCount(for token: OauthToken) -> Int { public func getNotificationsCount(for token: OauthToken) -> Int {
Self.sharedDefault?.integer(forKey: "push_notifications_count_\(token.createdAt)") ?? 0 Self.sharedDefault?.integer(forKey: "push_notifications_count_\(token.createdAt)") ?? 0
} }
public func getNotificationsTotalCount(for tokens: [OauthToken]) -> Int { public func getNotificationsTotalCount(for tokens: [OauthToken]) -> Int {
var count: Int = 0 var count = 0
for token in tokens { for token in tokens {
count += getNotificationsCount(for: token) count += getNotificationsCount(for: token)
} }
@ -134,6 +134,3 @@ public class UserPreferences: ObservableObject {
recentlyUsedLanguages = Array(copy.prefix(3)) recentlyUsedLanguages = Array(copy.prefix(3))
} }
} }

View file

@ -28,13 +28,11 @@ public struct Poll: Codable, Equatable, Hashable {
public let voted: Bool? public let voted: Bool?
public let ownVotes: [Int]? public let ownVotes: [Int]?
public let options: [Option] public let options: [Option]
// the votersCount can be null according to the docs when multiple is false. // the votersCount can be null according to the docs when multiple is false.
// Didn't find that to be true, but we make sure // Didn't find that to be true, but we make sure
public var safeVotersCount: Int { public var safeVotersCount: Int {
get { return votersCount ?? votesCount
return votersCount ?? votesCount
}
} }
} }

View file

@ -56,8 +56,8 @@ public protocol AnyStatus {
} }
public struct StatusViewId: Hashable { public struct StatusViewId: Hashable {
let id: String let id: String
let editedAt: ServerDate? let editedAt: ServerDate?
} }
public extension AnyStatus { public extension AnyStatus {

View file

@ -164,7 +164,7 @@ public struct MuteData: Encodable, Sendable {
public struct RelationshipNoteData: Encodable, Sendable { public struct RelationshipNoteData: Encodable, Sendable {
public let comment: String public let comment: String
public init(note comment: String) { public init(note comment: String) {
self.comment = comment self.comment = comment
} }

View file

@ -26,7 +26,7 @@ public struct StatusPollView: View {
return 0 return 0
} }
} }
private func percentForOption(option: Poll.Option) -> Int { private func percentForOption(option: Poll.Option) -> Int {
let percent = ratioForOption(option: option) * 100 let percent = ratioForOption(option: option) * 100
return Int(round(percent)) return Int(round(percent))

View file

@ -43,10 +43,10 @@ public class StatusRowViewModel: ObservableObject {
var filter: Filtered? { var filter: Filtered? {
status.reblog?.filtered?.first ?? status.filtered?.first status.reblog?.filtered?.first ?? status.filtered?.first
} }
var isThread: Bool { var isThread: Bool {
status.reblog?.inReplyToId != nil || status.reblog?.inReplyToAccountId != nil || status.reblog?.inReplyToId != nil || status.reblog?.inReplyToAccountId != nil ||
status.inReplyToId != nil || status.inReplyToAccountId != nil status.inReplyToId != nil || status.inReplyToAccountId != nil
} }
var highlightRowColor: Color { var highlightRowColor: Color {

View file

@ -8,11 +8,11 @@ struct StatusRowActionsView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount
@ObservedObject var viewModel: StatusRowViewModel @ObservedObject var viewModel: StatusRowViewModel
func privateBoost() -> Bool { func privateBoost() -> Bool {
return self.viewModel.status.visibility == .priv && self.viewModel.status.account.id == self.currentAccount.account?.id return viewModel.status.visibility == .priv && viewModel.status.account.id == currentAccount.account?.id
} }
@MainActor @MainActor
enum Actions: CaseIterable { enum Actions: CaseIterable {
case respond, boost, favorite, bookmark, share case respond, boost, favorite, bookmark, share
@ -22,10 +22,10 @@ struct StatusRowActionsView: View {
case .respond: case .respond:
return "arrowshape.turn.up.left" return "arrowshape.turn.up.left"
case .boost: case .boost:
if (privateBoost) { if privateBoost {
return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "lock.rotation" return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "lock.rotation"
} }
return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "arrow.left.arrow.right.circle" return viewModel.isReblogged ? "arrow.left.arrow.right.circle.fill" : "arrow.left.arrow.right.circle"
case .favorite: case .favorite:
return viewModel.isFavorited ? "star.fill" : "star" return viewModel.isFavorited ? "star.fill" : "star"
@ -96,7 +96,7 @@ struct StatusRowActionsView: View {
} }
.buttonStyle(.borderless) .buttonStyle(.borderless)
.disabled(action == .boost && .disabled(action == .boost &&
(viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id)) (viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id))
Spacer() Spacer()
} }
} }

View file

@ -8,7 +8,7 @@ import SwiftUI
public struct StatusRowCardView: View { public struct StatusRowCardView: View {
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
let card: Card let card: Card

View file

@ -1,19 +1,19 @@
import DesignSystem
import Env import Env
import Foundation import Foundation
import SwiftUI
import DesignSystem
import Network import Network
import SwiftUI
struct StatusRowContextMenu: View { struct StatusRowContextMenu: View {
@Environment(\.displayScale) var displayScale @Environment(\.displayScale) var displayScale
@EnvironmentObject private var sceneDelegate: SceneDelegate @EnvironmentObject private var sceneDelegate: SceneDelegate
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var account: CurrentAccount @EnvironmentObject private var account: CurrentAccount
@EnvironmentObject private var currentInstance: CurrentInstance @EnvironmentObject private var currentInstance: CurrentInstance
@ObservedObject var viewModel: StatusRowViewModel @ObservedObject var viewModel: StatusRowViewModel
var boostLabel: some View { var boostLabel: some View {
if self.viewModel.status.visibility == .priv && self.viewModel.status.account.id == self.account.account?.id { if self.viewModel.status.visibility == .priv && self.viewModel.status.account.id == self.account.account?.id {
if self.viewModel.isReblogged { if self.viewModel.isReblogged {
@ -21,13 +21,13 @@ struct StatusRowContextMenu: View {
} }
return Label("status.action.boost-to-followers", systemImage: "lock.rotation") return Label("status.action.boost-to-followers", systemImage: "lock.rotation")
} }
if self.viewModel.isReblogged { if self.viewModel.isReblogged {
return Label("status.action.unboost", systemImage: "arrow.left.arrow.right.circle") return Label("status.action.unboost", systemImage: "arrow.left.arrow.right.circle")
} }
return Label("status.action.boost", systemImage: "arrow.left.arrow.right.circle") return Label("status.action.boost", systemImage: "arrow.left.arrow.right.circle")
} }
var body: some View { var body: some View {
if !viewModel.isRemote { if !viewModel.isRemote {
Button { Task { Button { Task {
@ -85,11 +85,11 @@ struct StatusRowContextMenu: View {
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) { message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText)) {
Label("status.action.share", systemImage: "square.and.arrow.up") Label("status.action.share", systemImage: "square.and.arrow.up")
} }
ShareLink(item: url) { ShareLink(item: url) {
Label("status.action.share-link", systemImage: "link") Label("status.action.share-link", systemImage: "link")
} }
Button { Button {
let view = HStack { let view = HStack {
StatusRowView(viewModel: viewModel) StatusRowView(viewModel: viewModel)
@ -200,10 +200,10 @@ struct StatusRowContextMenu: View {
struct ActivityView: UIViewControllerRepresentable { struct ActivityView: UIViewControllerRepresentable {
let image: Image let image: Image
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController { func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
return UIActivityViewController(activityItems: [image], applicationActivities: nil) return UIActivityViewController(activityItems: [image], applicationActivities: nil)
} }
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {} func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
} }

View file

@ -1,7 +1,7 @@
import DesignSystem import DesignSystem
import Env
import Models import Models
import SwiftUI import SwiftUI
import Env
struct StatusRowHeaderView: View { struct StatusRowHeaderView: View {
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@ -57,19 +57,19 @@ struct StatusRowHeaderView: View {
} }
if theme.avatarPosition == .top { if theme.avatarPosition == .top {
dateView dateView
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundColor(.gray) .foregroundColor(.gray)
.lineLimit(1) .lineLimit(1)
} }
} }
} }
} }
private var dateView: Text { private var dateView: Text {
Text(viewModel.status.account.bot ? "🤖 " : "") + Text(viewModel.status.account.bot ? "🤖 " : "") +
Text(status.createdAt.relativeFormatted) + Text(status.createdAt.relativeFormatted) +
Text("") + Text("") +
Text(Image(systemName: viewModel.status.visibility.iconName)) Text(Image(systemName: viewModel.status.visibility.iconName))
} }
@ViewBuilder @ViewBuilder

View file

@ -156,7 +156,8 @@ public struct StatusRowMediaPreviewView: View {
case .image: case .image:
if isInCaptureMode, if isInCaptureMode,
let image = Nuke.ImagePipeline.shared.cache.cachedImage(for: .init(url: attachment.url, let image = Nuke.ImagePipeline.shared.cache.cachedImage(for: .init(url: attachment.url,
processors: processors))?.image { processors: processors))?.image
{
Image(uiImage: image) Image(uiImage: image)
.resizable() .resizable()
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: .fill)
@ -243,10 +244,11 @@ public struct StatusRowMediaPreviewView: View {
cornerSensitiveButton cornerSensitiveButton
} }
if !isInCaptureMode, if !isInCaptureMode,
let alt = attachment.description, let alt = attachment.description,
!alt.isEmpty, !alt.isEmpty,
!isNotifications, !isNotifications,
preferences.showAltTextForMedia { preferences.showAltTextForMedia
{
Button { Button {
altTextDisplayed = alt altTextDisplayed = alt
isAltAlertDisplayed = true isAltAlertDisplayed = true

View file

@ -4,7 +4,7 @@ import SwiftUI
struct StatusRowTextView: View { struct StatusRowTextView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
let status: AnyStatus let status: AnyStatus
let viewModel: StatusRowViewModel let viewModel: StatusRowViewModel

View file

@ -5,7 +5,7 @@ import SwiftUI
struct StatusRowTranslateView: View { struct StatusRowTranslateView: View {
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
let status: AnyStatus let status: AnyStatus
@ -27,7 +27,7 @@ struct StatusRowTranslateView: View {
var body: some View { var body: some View {
if !isInCaptureMode, if !isInCaptureMode,
let userLang = preferences.serverPreferences?.postLanguage, let userLang = preferences.serverPreferences?.postLanguage,
shouldShowTranslateButton shouldShowTranslateButton
{ {
Button { Button {

View file

@ -3,51 +3,51 @@ import Models
actor TimelineDatasource { actor TimelineDatasource {
private var statuses: [Status] = [] private var statuses: [Status] = []
var isEmpty: Bool { var isEmpty: Bool {
statuses.isEmpty statuses.isEmpty
} }
func get() -> [Status] { func get() -> [Status] {
statuses statuses
} }
func reset() { func reset() {
statuses = [] statuses = []
} }
func indexOf(statusId: String) -> Int? { func indexOf(statusId: String) -> Int? {
statuses.firstIndex(where: { $0.id == statusId }) statuses.firstIndex(where: { $0.id == statusId })
} }
func contains(statusId: String) -> Bool { func contains(statusId: String) -> Bool {
statuses.contains(where: { $0.id == statusId }) statuses.contains(where: { $0.id == statusId })
} }
func set(_ statuses: [Status]) { func set(_ statuses: [Status]) {
self.statuses = statuses self.statuses = statuses
} }
func append(_ status: Status) { func append(_ status: Status) {
statuses.append(status) statuses.append(status)
} }
func append(contentOf: [Status]) { func append(contentOf: [Status]) {
statuses.append(contentsOf: contentOf) statuses.append(contentsOf: contentOf)
} }
func insert(_ status: Status, at: Int) { func insert(_ status: Status, at: Int) {
statuses.insert(status, at: at) statuses.insert(status, at: at)
} }
func insert(contentOf: [Status], at: Int) { func insert(contentOf: [Status], at: Int) {
statuses.insert(contentsOf: contentOf, at: at) statuses.insert(contentsOf: contentOf, at: at)
} }
func replace(_ status: Status, at: Int) { func replace(_ status: Status, at: Int) {
statuses[at] = status statuses[at] = status
} }
func remove(_ statusId: String) { func remove(_ statusId: String) {
statuses.removeAll(where: { $0.id == statusId }) statuses.removeAll(where: { $0.id == statusId })
} }

View file

@ -1,25 +1,25 @@
import SwiftUI
import UIKit
import Models import Models
import Nuke import Nuke
import SwiftUI
import UIKit
final class TimelinePrefetcher: NSObject, ObservableObject, UICollectionViewDataSourcePrefetching { final class TimelinePrefetcher: NSObject, ObservableObject, UICollectionViewDataSourcePrefetching {
private let prefetcher = ImagePrefetcher() private let prefetcher = ImagePrefetcher()
weak var viewModel: TimelineViewModel? weak var viewModel: TimelineViewModel?
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { func collectionView(_: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
let imageURLs = getImageURLs(for: indexPaths) let imageURLs = getImageURLs(for: indexPaths)
prefetcher.startPrefetching(with: imageURLs) prefetcher.startPrefetching(with: imageURLs)
} }
func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { func collectionView(_: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
let imageURLs = getImageURLs(for: indexPaths) let imageURLs = getImageURLs(for: indexPaths)
prefetcher.stopPrefetching(with: imageURLs) prefetcher.stopPrefetching(with: imageURLs)
} }
private func getImageURLs(for indexPaths: [IndexPath]) -> [URL] { private func getImageURLs(for indexPaths: [IndexPath]) -> [URL] {
guard let viewModel, case .display(let statuses, _) = viewModel.statusesState else { guard let viewModel, case let .display(statuses, _) = viewModel.statusesState else {
return [] return []
} }
return indexPaths.compactMap { return indexPaths.compactMap {

View file

@ -92,7 +92,7 @@ class TimelineViewModel: ObservableObject {
tag = try await client.get(endpoint: Tags.tag(id: id)) tag = try await client.get(endpoint: Tags.tag(id: id))
} catch {} } catch {}
} }
func reset() async { func reset() async {
await datasource.reset() await datasource.reset()
} }
@ -214,7 +214,7 @@ extension TimelineViewModel: StatusesFetcher {
updateMentionsToBeHighlighted(&statuses) updateMentionsToBeHighlighted(&statuses)
ReblogCache.shared.removeDuplicateReblogs(&statuses) ReblogCache.shared.removeDuplicateReblogs(&statuses)
await datasource.set(statuses) await datasource.set(statuses)
await cacheHome() await cacheHome()
@ -231,7 +231,7 @@ extension TimelineViewModel: StatusesFetcher {
var newStatuses: [Status] = await fetchNewPages(minId: latestStatus.id, maxPages: 10) var newStatuses: [Status] = await fetchNewPages(minId: latestStatus.id, maxPages: 10)
// Dedup statuses, a status with the same id could have been streamed in. // Dedup statuses, a status with the same id could have been streamed in.
let ids = await datasource.get().map{ $0.id } let ids = await datasource.get().map { $0.id }
newStatuses = newStatuses.filter { status in newStatuses = newStatuses.filter { status in
!ids.contains(where: { $0 == status.id }) !ids.contains(where: { $0 == status.id })
} }
@ -322,10 +322,10 @@ extension TimelineViewModel: StatusesFetcher {
var latestMinId = minId var latestMinId = minId
do { do {
while var newStatuses: [Status] = while var newStatuses: [Status] =
try await client.get(endpoint: timeline.endpoint(sinceId: nil, try await client.get(endpoint: timeline.endpoint(sinceId: nil,
maxId: nil, maxId: nil,
minId: latestMinId, minId: latestMinId,
offset: datasource.get().count)), offset: datasource.get().count)),
!newStatuses.isEmpty, !newStatuses.isEmpty,
pagesLoaded < maxPages pagesLoaded < maxPages
{ {