mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-26 10:11:00 +00:00
format
This commit is contained in:
parent
4c7a7986c5
commit
3e3c69c41c
30 changed files with 142 additions and 147 deletions
|
@ -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 {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import SwiftUI
|
|
||||||
import Env
|
import Env
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
extension IceCubesApp {
|
extension IceCubesApp {
|
||||||
@CommandsBuilder
|
@CommandsBuilder
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue