This commit is contained in:
Thomas Ricouard 2023-11-01 18:58:44 +01:00
parent 4c7a7986c5
commit 3e3c69c41c
30 changed files with 142 additions and 147 deletions

View file

@ -6,11 +6,11 @@ import Env
import Explore import Explore
import LinkPresentation import LinkPresentation
import Lists import Lists
import MediaUI
import Models import Models
import Status import Status
import SwiftUI import SwiftUI
import Timeline import Timeline
import MediaUI
@MainActor @MainActor
extension View { extension View {

View file

@ -1,5 +1,5 @@
import SwiftUI
import Env import Env
import SwiftUI
extension IceCubesApp { extension IceCubesApp {
@CommandsBuilder @CommandsBuilder

View file

@ -1,7 +1,7 @@
import SwiftUI
import Env import Env
import Status
import MediaUI import MediaUI
import Status
import SwiftUI
extension IceCubesApp { extension IceCubesApp {
var appScene: some Scene { var appScene: some Scene {
@ -26,9 +26,9 @@ extension IceCubesApp {
.sheet(item: $quickLook.selectedMediaAttachment) { selectedMediaAttachment in .sheet(item: $quickLook.selectedMediaAttachment) { selectedMediaAttachment in
MediaUIView(selectedAttachment: selectedMediaAttachment, MediaUIView(selectedAttachment: selectedMediaAttachment,
attachments: quickLook.mediaAttachments) attachments: quickLook.mediaAttachments)
.presentationBackground(.ultraThinMaterial) .presentationBackground(.ultraThinMaterial)
.presentationCornerRadius(16) .presentationCornerRadius(16)
.withEnvironments() .withEnvironments()
} }
.onChange(of: pushNotificationsService.handledNotification) { _, newValue in .onChange(of: pushNotificationsService.handledNotification) { _, newValue in
if newValue != nil { if newValue != nil {
@ -72,7 +72,6 @@ extension IceCubesApp {
} }
} }
var otherScenes: some Scene { var otherScenes: some Scene {
WindowGroup(for: WindowDestination.self) { destination in WindowGroup(for: WindowDestination.self) { destination in
Group { Group {

View file

@ -1,5 +1,5 @@
import SwiftUI
import Env import Env
import SwiftUI
extension IceCubesApp { extension IceCubesApp {
var sidebarView: some View { var sidebarView: some View {
@ -46,5 +46,4 @@ extension IceCubesApp {
.frame(maxWidth: .secondaryColumnWidth) .frame(maxWidth: .secondaryColumnWidth)
.id(appAccountsManager.currentAccount.id) .id(appAccountsManager.currentAccount.id)
} }
} }

View file

@ -1,5 +1,5 @@
import SwiftUI
import Env import Env
import SwiftUI
extension IceCubesApp { extension IceCubesApp {
var tabBarView: some View { var tabBarView: some View {
@ -45,8 +45,7 @@ extension IceCubesApp {
} }
.id(appAccountsManager.currentClient.id) .id(appAccountsManager.currentClient.id)
} }
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

View file

@ -4,12 +4,12 @@ import AVFoundation
import DesignSystem import DesignSystem
import Env import Env
import KeychainSwift import KeychainSwift
import MediaUI
import Network import Network
import RevenueCat import RevenueCat
import Status import Status
import SwiftUI import SwiftUI
import Timeline import Timeline
import MediaUI
@main @main
struct IceCubesApp: App { struct IceCubesApp: App {
@ -41,7 +41,7 @@ struct IceCubesApp: App {
appScene appScene
otherScenes otherScenes
} }
func setNewClientsInEnv(client: Client) { func setNewClientsInEnv(client: Client) {
currentAccount.setClient(client: client) currentAccount.setClient(client: client)
currentInstance.setClient(client: client) currentInstance.setClient(client: client)

View file

@ -8,7 +8,7 @@ import SwiftUI
@MainActor @MainActor
struct SideBarView<Content: View>: View { struct SideBarView<Content: View>: View {
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
@Environment(AppAccountsManager.self) private var appAccounts @Environment(AppAccountsManager.self) private var appAccounts
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme

View file

@ -169,7 +169,7 @@ struct SettingsTabs: View {
private var otherSections: some View { private var otherSections: some View {
@Bindable var preferences = preferences @Bindable var preferences = preferences
Section("settings.section.other") { Section("settings.section.other") {
if !ProcessInfo.processInfo.isMacCatalystApp{ if !ProcessInfo.processInfo.isMacCatalystApp {
Picker(selection: $preferences.preferredBrowser) { Picker(selection: $preferences.preferredBrowser) {
ForEach(PreferredBrowser.allCases, id: \.rawValue) { browser in ForEach(PreferredBrowser.allCases, id: \.rawValue) { browser in
switch browser { switch browser {

View file

@ -7,7 +7,7 @@ public struct AccountDetailContextMenu: View {
@Environment(RouterPath.self) private var routerPath @Environment(RouterPath.self) private var routerPath
@Environment(CurrentInstance.self) private var currentInstance @Environment(CurrentInstance.self) private var currentInstance
@Environment(UserPreferences.self) private var preferences @Environment(UserPreferences.self) private var preferences
@Binding var showBlockConfirmation: Bool @Binding var showBlockConfirmation: Bool
var viewModel: AccountDetailViewModel var viewModel: AccountDetailViewModel

View file

@ -81,7 +81,7 @@ struct AccountDetailHeaderView: View {
return return
} }
let attachement = MediaAttachment.imageWith(url: account.header) let attachement = MediaAttachment.imageWith(url: account.header)
if ProcessInfo.processInfo.isMacCatalystApp { if ProcessInfo.processInfo.isMacCatalystApp {
openWindow(value: WindowDestination.mediaViewer(attachments: [attachement], openWindow(value: WindowDestination.mediaViewer(attachments: [attachement],
selectedAttachment: attachement)) selectedAttachment: attachement))

View file

@ -106,11 +106,10 @@ struct ConversationMessageView: View {
Button { Button {
Task { Task {
do { do {
let status: Status let status: Status = if isLiked {
if isLiked { try await client.post(endpoint: Statuses.unfavorite(id: message.id))
status = try await client.post(endpoint: Statuses.unfavorite(id: message.id))
} else { } else {
status = try await client.post(endpoint: Statuses.favorite(id: message.id)) try await client.post(endpoint: Statuses.favorite(id: message.id))
} }
withAnimation { withAnimation {
isLiked = status.favourited == true isLiked = status.favourited == true
@ -123,11 +122,10 @@ struct ConversationMessageView: View {
} }
Button { Task { Button { Task {
do { do {
let status: Status let status: Status = if isBookmarked {
if isBookmarked { try await client.post(endpoint: Statuses.unbookmark(id: message.id))
status = try await client.post(endpoint: Statuses.unbookmark(id: message.id))
} else { } else {
status = try await client.post(endpoint: Statuses.bookmark(id: message.id)) try await client.post(endpoint: Statuses.bookmark(id: message.id))
} }
withAnimation { withAnimation {
isBookmarked = status.bookmarked == true isBookmarked = status.bookmarked == true

View file

@ -14,12 +14,12 @@ import UIKit
{ {
guard let windowScene = scene as? UIWindowScene else { return } guard let windowScene = scene as? UIWindowScene else { return }
window = windowScene.keyWindow window = windowScene.keyWindow
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
if let titlebar = windowScene.titlebar { if let titlebar = windowScene.titlebar {
titlebar.titleVisibility = .hidden titlebar.titleVisibility = .hidden
titlebar.toolbar = nil titlebar.toolbar = nil
} }
#endif #endif
} }
} }

View file

@ -10,33 +10,33 @@ import NukeUI
import SwiftUI import SwiftUI
/// A LazyImage (Nuke) with a geometry reader under the hood in order to use a Resize Processor to optimize performances on lists. /// A LazyImage (Nuke) with a geometry reader under the hood in order to use a Resize Processor to optimize performances on lists.
/// This views also allows smooth resizing of the images by debouncing the update of the ImageProcessor. /// This views also allows smooth resizing of the images by debouncing the update of the ImageProcessor.
public struct LazyResizableImage<Content: View>: View { public struct LazyResizableImage<Content: View>: View {
public init(url: URL?, @ViewBuilder content: @escaping (LazyImageState, GeometryProxy) -> Content) { public init(url: URL?, @ViewBuilder content: @escaping (LazyImageState, GeometryProxy) -> Content) {
self.imageURL = url imageURL = url
self.content = content self.content = content
} }
let imageURL: URL? let imageURL: URL?
@State private var resizeProcessor: ImageProcessors.Resize? @State private var resizeProcessor: ImageProcessors.Resize?
@State private var debouncedTask: Task<Void, Never>? @State private var debouncedTask: Task<Void, Never>?
@ViewBuilder @ViewBuilder
private var content: (LazyImageState, _ proxy: GeometryProxy) -> Content private var content: (LazyImageState, _ proxy: GeometryProxy) -> Content
public var body: some View { public var body: some View {
GeometryReader { proxy in GeometryReader { proxy in
LazyImage(url: imageURL) { state in LazyImage(url: imageURL) { state in
content(state, proxy) content(state, proxy)
} }
.processors([resizeProcessor == nil ? .resize(size: proxy.size) : resizeProcessor!]) .processors([resizeProcessor == nil ? .resize(size: proxy.size) : resizeProcessor!])
.onChange(of: proxy.size, initial: true) { oldValue, newValue in .onChange(of: proxy.size, initial: true) { _, newValue in
debouncedTask?.cancel() debouncedTask?.cancel()
debouncedTask = Task { debouncedTask = Task {
do { try await Task.sleep(for: .milliseconds(200)) } catch { return } do { try await Task.sleep(for: .milliseconds(200)) } catch { return }
resizeProcessor = .resize(size: newValue) resizeProcessor = .resize(size: newValue)
}
}
} }
}
} }
}
} }

View file

@ -4,8 +4,9 @@ import SwiftUI
@MainActor @MainActor
public extension View { public extension View {
func statusEditorToolbarItem(routerPath: RouterPath, func statusEditorToolbarItem(routerPath _: RouterPath,
visibility: Models.Visibility) -> some ToolbarContent { visibility: Models.Visibility) -> some ToolbarContent
{
StatusEditorToolbarItem(visibility: visibility) StatusEditorToolbarItem(visibility: visibility)
} }
} }

View file

@ -6,11 +6,11 @@ import QuickLook
@Observable public class QuickLook { @Observable public class QuickLook {
public var selectedMediaAttachment: MediaAttachment? public var selectedMediaAttachment: MediaAttachment?
public var mediaAttachments: [MediaAttachment] = [] public var mediaAttachments: [MediaAttachment] = []
public static let shared = QuickLook() public static let shared = QuickLook()
private init() {} private init() {}
public func prepareFor(selectedMediaAttachment: MediaAttachment, mediaAttachments: [MediaAttachment]) { public func prepareFor(selectedMediaAttachment: MediaAttachment, mediaAttachments: [MediaAttachment]) {
self.selectedMediaAttachment = selectedMediaAttachment self.selectedMediaAttachment = selectedMediaAttachment
self.mediaAttachments = mediaAttachments self.mediaAttachments = mediaAttachments

View file

@ -80,28 +80,26 @@ import SwiftUI
storage.pendingShownAtBottom = pendingShownAtBottom storage.pendingShownAtBottom = pendingShownAtBottom
} }
} }
public var pendingShownLeft: Bool { public var pendingShownLeft: Bool {
didSet { didSet {
storage.pendingShownLeft = pendingShownLeft storage.pendingShownLeft = pendingShownLeft
} }
} }
public var pendingLocation: Alignment { public var pendingLocation: Alignment {
get { let fromLeft = Locale.current.language.characterDirection == .leftToRight ? pendingShownLeft : !pendingShownLeft
let fromLeft = Locale.current.language.characterDirection == .leftToRight ? pendingShownLeft : !pendingShownLeft if pendingShownAtBottom {
if pendingShownAtBottom { if fromLeft {
if fromLeft { return .bottomLeading
return .bottomLeading
} else {
return .bottomTrailing
}
} else { } else {
if fromLeft { return .bottomTrailing
return .topLeading }
} else { } else {
return .topTrailing if fromLeft {
} return .topLeading
} else {
return .topTrailing
} }
} }
} }
@ -375,7 +373,7 @@ import SwiftUI
} }
public var totalNotificationsCount: Int { public var totalNotificationsCount: Int {
notificationsCount.compactMap{ $0.value }.reduce(0, +) notificationsCount.compactMap(\.value).reduce(0, +)
} }
public func reloadNotificationsCount(tokens: [OauthToken]) { public func reloadNotificationsCount(tokens: [OauthToken]) {
@ -456,6 +454,6 @@ import SwiftUI
collapseLongPosts = storage.collapseLongPosts collapseLongPosts = storage.collapseLongPosts
shareButtonBehavior = storage.shareButtonBehavior shareButtonBehavior = storage.shareButtonBehavior
pendingShownAtBottom = storage.pendingShownAtBottom pendingShownAtBottom = storage.pendingShownAtBottom
pendingShownLeft = storage.pendingShownLeft pendingShownLeft = storage.pendingShownLeft
} }
} }

View file

@ -1,12 +1,12 @@
import SwiftUI
import Models import Models
import NukeUI import NukeUI
import SwiftUI
struct MediaUIAttachmentImageView: View { struct MediaUIAttachmentImageView: View {
let url: URL let url: URL
@GestureState private var zoom = 1.0 @GestureState private var zoom = 1.0
var body: some View { var body: some View {
MediaUIZoomableContainer { MediaUIZoomableContainer {
LazyImage(url: url) { state in LazyImage(url: url) { state in

View file

@ -57,7 +57,7 @@ public struct MediaUIAttachmentVideoView: View {
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme
@State var viewModel: MediaUIAttachmentVideoViewModel @State var viewModel: MediaUIAttachmentVideoViewModel
public init(viewModel: MediaUIAttachmentVideoViewModel) { public init(viewModel: MediaUIAttachmentVideoViewModel) {
_viewModel = .init(wrappedValue: viewModel) _viewModel = .init(wrappedValue: viewModel)
} }
@ -67,7 +67,7 @@ public struct MediaUIAttachmentVideoView: View {
VideoPlayer(player: viewModel.player) VideoPlayer(player: viewModel.player)
.accessibilityAddTraits(.startsMediaSession) .accessibilityAddTraits(.startsMediaSession)
if !preferences.autoPlayVideo && !viewModel.forceAutoPlay { if !preferences.autoPlayVideo, !viewModel.forceAutoPlay {
Image(systemName: "play.fill") Image(systemName: "play.fill")
.font(isCompact ? .body : .largeTitle) .font(isCompact ? .body : .largeTitle)
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)

View file

@ -1,10 +1,10 @@
import CoreTransferable
import SwiftUI import SwiftUI
import UIKit import UIKit
import CoreTransferable
struct MediaUIImageTransferable: Codable, Transferable { struct MediaUIImageTransferable: Codable, Transferable {
let url: URL let url: URL
func fetchAsImage() async -> Image { func fetchAsImage() async -> Image {
let data = try? await URLSession.shared.data(from: url).0 let data = try? await URLSession.shared.data(from: url).0
guard let data, let uiimage = UIImage(data: data) else { guard let data, let uiimage = UIImage(data: data) else {
@ -12,7 +12,7 @@ struct MediaUIImageTransferable: Codable, Transferable {
} }
return Image(uiImage: uiimage) return Image(uiImage: uiimage)
} }
static var transferRepresentation: some TransferRepresentation { static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation { media in ProxyRepresentation { media in
await media.fetchAsImage() await media.fetchAsImage()

View file

@ -1,7 +1,7 @@
import Nuke
import SwiftUI
import Models import Models
import Nuke
import QuickLook import QuickLook
import SwiftUI
public struct MediaUIView: View, @unchecked Sendable { public struct MediaUIView: View, @unchecked Sendable {
private let data: [DisplayData] private let data: [DisplayData]
@ -36,8 +36,8 @@ public struct MediaUIView: View, @unchecked Sendable {
} }
public init(selectedAttachment: MediaAttachment, attachments: [MediaAttachment]) { public init(selectedAttachment: MediaAttachment, attachments: [MediaAttachment]) {
self.data = attachments.compactMap { DisplayData(from: $0) } data = attachments.compactMap { DisplayData(from: $0) }
self.initialItem = DisplayData(from: selectedAttachment) initialItem = DisplayData(from: selectedAttachment)
} }
} }
@ -45,9 +45,9 @@ private struct MediaToolBar: ToolbarContent {
let data: DisplayData let data: DisplayData
var body: some ToolbarContent { var body: some ToolbarContent {
#if !targetEnvironment(macCatalyst) #if !targetEnvironment(macCatalyst)
DismissToolbarItem() DismissToolbarItem()
#endif #endif
QuickLookToolbarItem(itemUrl: data.url) QuickLookToolbarItem(itemUrl: data.url)
AltTextToolbarItem(alt: data.description) AltTextToolbarItem(alt: data.description)
SavePhotoToolbarItem(url: data.url, type: data.type) SavePhotoToolbarItem(url: data.url, type: data.type)
@ -75,15 +75,15 @@ private struct AltTextToolbarItem: ToolbarContent {
var body: some ToolbarContent { var body: some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
if let alt = alt { if let alt {
Button { Button {
isAlertDisplayed = true isAlertDisplayed = true
} label: { } label: {
Text("status.image.alt-text.abbreviation") Text("status.image.alt-text.abbreviation")
} }
.alert("status.editor.media.image-description", .alert("status.editor.media.image-description",
isPresented: $isAlertDisplayed isPresented: $isAlertDisplayed)
) { {
Button("alert.button.ok", action: {}) Button("alert.button.ok", action: {})
} message: { } message: {
Text(alt) Text(alt)
@ -118,8 +118,8 @@ private struct SavePhotoToolbarItem: ToolbarContent, @unchecked Sendable {
} label: { } label: {
switch state { switch state {
case .unsaved: Image(systemName: "arrow.down.circle") case .unsaved: Image(systemName: "arrow.down.circle")
case .saving : ProgressView() case .saving: ProgressView()
case .saved : Image(systemName: "checkmark.circle.fill") case .saved: Image(systemName: "checkmark.circle.fill")
} }
} }
} else { } else {
@ -205,7 +205,7 @@ private struct QuickLookToolbarItem: ToolbarContent, @unchecked Sendable {
in: .userDomainMask, in: .userDomainMask,
appropriateFor: nil, appropriateFor: nil,
create: false) create: false)
.appending(component: "quicklook") .appending(component: "quicklook")
} }
} }
@ -236,9 +236,9 @@ private struct DisplayData: Identifiable, Hashable {
guard let url = attachment.url else { return nil } guard let url = attachment.url else { return nil }
guard let type = attachment.supportedType else { return nil } guard let type = attachment.supportedType else { return nil }
self.id = attachment.id id = attachment.id
self.url = url self.url = url
self.description = attachment.description description = attachment.description
self.type = DisplayType(from: type) self.type = DisplayType(from: type)
} }
} }

View file

@ -3,41 +3,41 @@ import UIKit
// ref: https://stackoverflow.com/questions/74238414/is-there-an-easy-way-to-pinch-to-zoom-and-drag-any-view-in-swiftui // ref: https://stackoverflow.com/questions/74238414/is-there-an-easy-way-to-pinch-to-zoom-and-drag-any-view-in-swiftui
fileprivate let maxAllowedScale = 4.0 private let maxAllowedScale = 4.0
@MainActor @MainActor
struct MediaUIZoomableContainer<Content: View>: View { struct MediaUIZoomableContainer<Content: View>: View {
let content: Content let content: Content
@State private var currentScale: CGFloat = 1.0 @State private var currentScale: CGFloat = 1.0
@State private var tapLocation: CGPoint = .zero @State private var tapLocation: CGPoint = .zero
init(@ViewBuilder content: () -> Content) { init(@ViewBuilder content: () -> Content) {
self.content = content() self.content = content()
} }
func doubleTapAction(location: CGPoint) { func doubleTapAction(location: CGPoint) {
tapLocation = location tapLocation = location
currentScale = currentScale == 1.0 ? maxAllowedScale : 1.0 currentScale = currentScale == 1.0 ? maxAllowedScale : 1.0
} }
var body: some View { var body: some View {
ZoomableScrollView(scale: $currentScale, tapLocation: $tapLocation) { ZoomableScrollView(scale: $currentScale, tapLocation: $tapLocation) {
content content
} }
.onTapGesture(count: 2, perform: doubleTapAction) .onTapGesture(count: 2, perform: doubleTapAction)
} }
fileprivate struct ZoomableScrollView<ScollContent: View>: UIViewRepresentable { fileprivate struct ZoomableScrollView<ScollContent: View>: UIViewRepresentable {
private var content: ScollContent private var content: ScollContent
@Binding private var currentScale: CGFloat @Binding private var currentScale: CGFloat
@Binding private var tapLocation: CGPoint @Binding private var tapLocation: CGPoint
init(scale: Binding<CGFloat>, tapLocation: Binding<CGPoint>, @ViewBuilder content: () -> ScollContent) { init(scale: Binding<CGFloat>, tapLocation: Binding<CGPoint>, @ViewBuilder content: () -> ScollContent) {
_currentScale = scale _currentScale = scale
_tapLocation = tapLocation _tapLocation = tapLocation
self.content = content() self.content = content()
} }
func makeUIView(context: Context) -> UIScrollView { func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView() let scrollView = UIScrollView()
scrollView.backgroundColor = .clear scrollView.backgroundColor = .clear
@ -49,24 +49,24 @@ struct MediaUIZoomableContainer<Content: View>: View {
scrollView.showsVerticalScrollIndicator = false scrollView.showsVerticalScrollIndicator = false
scrollView.clipsToBounds = false scrollView.clipsToBounds = false
scrollView.backgroundColor = .clear scrollView.backgroundColor = .clear
let hostedView = context.coordinator.hostingController.view! let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight] hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds hostedView.frame = scrollView.bounds
hostedView.backgroundColor = .clear hostedView.backgroundColor = .clear
scrollView.addSubview(hostedView) scrollView.addSubview(hostedView)
return scrollView return scrollView
} }
func makeCoordinator() -> Coordinator { func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: content), scale: $currentScale) Coordinator(hostingController: UIHostingController(rootView: content), scale: $currentScale)
} }
func updateUIView(_ uiView: UIScrollView, context: Context) { func updateUIView(_ uiView: UIScrollView, context: Context) {
context.coordinator.hostingController.rootView = content context.coordinator.hostingController.rootView = content
if uiView.zoomScale > uiView.minimumZoomScale { // Scale out if uiView.zoomScale > uiView.minimumZoomScale { // Scale out
uiView.setZoomScale(currentScale, animated: true) uiView.setZoomScale(currentScale, animated: true)
} else if tapLocation != .zero { // Scale in to a specific point } else if tapLocation != .zero { // Scale in to a specific point
@ -74,32 +74,32 @@ struct MediaUIZoomableContainer<Content: View>: View {
DispatchQueue.main.async { tapLocation = .zero } DispatchQueue.main.async { tapLocation = .zero }
} }
} }
@MainActor func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect { @MainActor func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect {
let scrollViewSize = scrollView.bounds.size let scrollViewSize = scrollView.bounds.size
let width = scrollViewSize.width / scale let width = scrollViewSize.width / scale
let height = scrollViewSize.height / scale let height = scrollViewSize.height / scale
let x = center.x - (width / 2.0) let x = center.x - (width / 2.0)
let y = center.y - (height / 2.0) let y = center.y - (height / 2.0)
return CGRect(x: x, y: y, width: width, height: height) return CGRect(x: x, y: y, width: width, height: height)
} }
class Coordinator: NSObject, UIScrollViewDelegate { class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<ScollContent> var hostingController: UIHostingController<ScollContent>
@Binding var currentScale: CGFloat @Binding var currentScale: CGFloat
init(hostingController: UIHostingController<ScollContent>, scale: Binding<CGFloat>) { init(hostingController: UIHostingController<ScollContent>, scale: Binding<CGFloat>) {
self.hostingController = hostingController self.hostingController = hostingController
_currentScale = scale _currentScale = scale
} }
func viewForZooming(in scrollView: UIScrollView) -> UIView? { func viewForZooming(in _: UIScrollView) -> UIView? {
return hostingController.view hostingController.view
} }
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { func scrollViewDidEndZooming(_: UIScrollView, with _: UIView?, atScale scale: CGFloat) {
currentScale = scale currentScale = scale
} }
} }

View file

@ -44,14 +44,14 @@ public struct MediaAttachment: Codable, Identifiable, Hashable, Equatable {
public let previewUrl: URL? public let previewUrl: URL?
public let description: String? public let description: String?
public let meta: MetaContainer? public let meta: MetaContainer?
public static func imageWith(url: URL) -> MediaAttachment{ public static func imageWith(url: URL) -> MediaAttachment {
return .init(id: UUID().uuidString, .init(id: UUID().uuidString,
type: "image", type: "image",
url: url, url: url,
previewUrl: url, previewUrl: url,
description: nil, description: nil,
meta: nil) meta: nil)
} }
} }

View file

@ -4,7 +4,7 @@ import SwiftUI
@Model public class Draft { @Model public class Draft {
public var content: String = "" public var content: String = ""
public var creationDate: Date = Date() public var creationDate: Date = .init()
public init(content: String) { public init(content: String) {
self.content = content self.content = content

View file

@ -4,7 +4,7 @@ import SwiftUI
@Model public class LocalTimeline { @Model public class LocalTimeline {
public var instance: String = "" public var instance: String = ""
public var creationDate: Date = Date() public var creationDate: Date = .init()
public init(instance: String) { public init(instance: String) {
self.instance = instance self.instance = instance

View file

@ -6,7 +6,7 @@ import SwiftUI
public var title: String = "" public var title: String = ""
public var symbolName: String = "" public var symbolName: String = ""
public var tags: [String] = [] public var tags: [String] = []
public var creationDate: Date = Date() public var creationDate: Date = .init()
public init(title: String, symbolName: String, tags: [String]) { public init(title: String, symbolName: String, tags: [String]) {
self.title = title self.title = title

View file

@ -18,7 +18,7 @@ import SwiftUI
public enum Version: String, Sendable { public enum Version: String, Sendable {
case v1, v2 case v1, v2
} }
public enum ClientError: Error { public enum ClientError: Error {
case unexpectedRequest case unexpectedRequest
} }
@ -93,7 +93,8 @@ import SwiftUI
private func makeURL(scheme: String = "https", private func makeURL(scheme: String = "https",
endpoint: Endpoint, endpoint: Endpoint,
forceVersion: Version? = nil, forceVersion: Version? = nil,
forceServer: String? = nil) throws -> URL { forceServer: String? = nil) throws -> URL
{
var components = URLComponents() var components = URLComponents()
components.scheme = scheme components.scheme = scheme
components.host = forceServer ?? server components.host = forceServer ?? server
@ -140,7 +141,7 @@ import SwiftUI
} }
public func getWithLink<Entity: Decodable>(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) { public func getWithLink<Entity: Decodable>(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) {
let (data, httpResponse) = try await urlSession.data(for: try makeGet(endpoint: endpoint)) let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
var linkHandler: LinkHandler? var linkHandler: LinkHandler?
if let response = httpResponse as? HTTPURLResponse, if let response = httpResponse as? HTTPURLResponse,
let link = response.allHeaderFields["Link"] as? String let link = response.allHeaderFields["Link"] as? String

View file

@ -27,7 +27,7 @@ public struct StatusEditorView: View {
@State private var isDismissAlertPresented: Bool = false @State private var isDismissAlertPresented: Bool = false
@State private var isLanguageConfirmPresented = false @State private var isLanguageConfirmPresented = false
@State private var editingContainer: StatusEditorMediaContainer? @State private var editingContainer: StatusEditorMediaContainer?
public init(mode: StatusEditorViewModel.Mode) { public init(mode: StatusEditorViewModel.Mode) {
@ -290,7 +290,7 @@ public struct StatusEditorView: View {
) )
} }
} }
private func close() { private func close() {
if ProcessInfo.processInfo.isMacCatalystApp { if ProcessInfo.processInfo.isMacCatalystApp {
dismissWindow() dismissWindow()

View file

@ -211,7 +211,7 @@ public struct StatusRowView: View {
let attachments = viewModel.finalStatus.mediaAttachments let attachments = viewModel.finalStatus.mediaAttachments
if ProcessInfo.processInfo.isMacCatalystApp { if ProcessInfo.processInfo.isMacCatalystApp {
openWindow(value: WindowDestination.mediaViewer(attachments: attachments, openWindow(value: WindowDestination.mediaViewer(attachments: attachments,
selectedAttachment: attachments[0])) selectedAttachment: attachments[0]))
} else { } else {
quickLook.prepareFor(selectedMediaAttachment: attachments[0], mediaAttachments: attachments) quickLook.prepareFor(selectedMediaAttachment: attachments[0], mediaAttachments: attachments)
} }

View file

@ -1,10 +1,10 @@
import DesignSystem import DesignSystem
import Env import Env
import MediaUI
import Models import Models
import Nuke import Nuke
import NukeUI import NukeUI
import SwiftUI import SwiftUI
import MediaUI
@MainActor @MainActor
public struct StatusRowMediaPreviewView: View { public struct StatusRowMediaPreviewView: View {
@ -29,7 +29,7 @@ public struct StatusRowMediaPreviewView: View {
var availableWidth: CGFloat { var availableWidth: CGFloat {
if UIDevice.current.userInterfaceIdiom == .phone && if UIDevice.current.userInterfaceIdiom == .phone &&
(UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium
{ {
return sceneDelegate.windowWidth * 0.80 return sceneDelegate.windowWidth * 0.80
} }

View file

@ -4,9 +4,9 @@ import Models
import Network import Network
import Shimmer import Shimmer
import Status import Status
import SwiftData
import SwiftUI import SwiftUI
import SwiftUIIntrospect import SwiftUIIntrospect
import SwiftData
@MainActor @MainActor
public struct TimelineView: View { public struct TimelineView: View {
@ -26,9 +26,9 @@ public struct TimelineView: View {
@Binding var timeline: TimelineFilter @Binding var timeline: TimelineFilter
@Binding var selectedTagGroup: TagGroup? @Binding var selectedTagGroup: TagGroup?
@Binding var scrollToTopSignal: Int @Binding var scrollToTopSignal: Int
@Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup] @Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup]
private let canFilterTimeline: Bool private let canFilterTimeline: Bool
public init(timeline: Binding<TimelineFilter>, public init(timeline: Binding<TimelineFilter>,
@ -224,7 +224,7 @@ public struct TimelineView: View {
bottom: 8, bottom: 8,
trailing: .layoutPadding)) trailing: .layoutPadding))
} }
@ToolbarContentBuilder @ToolbarContentBuilder
private var toolbarTitleView: some ToolbarContent { private var toolbarTitleView: some ToolbarContent {
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
@ -262,7 +262,7 @@ public struct TimelineView: View {
.accessibilityRespondsToUserInteraction(canFilterTimeline) .accessibilityRespondsToUserInteraction(canFilterTimeline)
} }
} }
@ToolbarContentBuilder @ToolbarContentBuilder
private var toolbarTagGroupButton: some ToolbarContent { private var toolbarTagGroupButton: some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {