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 LinkPresentation
import Lists
import MediaUI
import Models
import Status
import SwiftUI
import Timeline
import MediaUI
@MainActor
extension View {

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import SwiftUI
import Env
import SwiftUI
extension IceCubesApp {
var tabBarView: some View {
@ -45,8 +45,7 @@ extension IceCubesApp {
}
.id(appAccountsManager.currentClient.id)
}
private func badgeFor(tab: Tab) -> Int {
if tab == .notifications, selectedTab != tab,
let token = appAccountsManager.currentAccount.oauthToken

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,33 +10,33 @@ import NukeUI
import SwiftUI
/// 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 init(url: URL?, @ViewBuilder content: @escaping (LazyImageState, GeometryProxy) -> Content) {
self.imageURL = url
self.content = content
}
public init(url: URL?, @ViewBuilder content: @escaping (LazyImageState, GeometryProxy) -> Content) {
imageURL = url
self.content = content
}
let imageURL: URL?
@State private var resizeProcessor: ImageProcessors.Resize?
@State private var debouncedTask: Task<Void, Never>?
let imageURL: URL?
@State private var resizeProcessor: ImageProcessors.Resize?
@State private var debouncedTask: Task<Void, Never>?
@ViewBuilder
private var content: (LazyImageState, _ proxy: GeometryProxy) -> Content
@ViewBuilder
private var content: (LazyImageState, _ proxy: GeometryProxy) -> Content
public var body: some View {
GeometryReader { proxy in
LazyImage(url: imageURL) { state in
content(state, proxy)
}
.processors([resizeProcessor == nil ? .resize(size: proxy.size) : resizeProcessor!])
.onChange(of: proxy.size, initial: true) { oldValue, newValue in
debouncedTask?.cancel()
debouncedTask = Task {
do { try await Task.sleep(for: .milliseconds(200)) } catch { return }
resizeProcessor = .resize(size: newValue)
}
}
public var body: some View {
GeometryReader { proxy in
LazyImage(url: imageURL) { state in
content(state, proxy)
}
.processors([resizeProcessor == nil ? .resize(size: proxy.size) : resizeProcessor!])
.onChange(of: proxy.size, initial: true) { _, newValue in
debouncedTask?.cancel()
debouncedTask = Task {
do { try await Task.sleep(for: .milliseconds(200)) } catch { return }
resizeProcessor = .resize(size: newValue)
}
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import Nuke
import SwiftUI
import Models
import Nuke
import QuickLook
import SwiftUI
public struct MediaUIView: View, @unchecked Sendable {
private let data: [DisplayData]
@ -36,8 +36,8 @@ public struct MediaUIView: View, @unchecked Sendable {
}
public init(selectedAttachment: MediaAttachment, attachments: [MediaAttachment]) {
self.data = attachments.compactMap { DisplayData(from: $0) }
self.initialItem = DisplayData(from: selectedAttachment)
data = attachments.compactMap { DisplayData(from: $0) }
initialItem = DisplayData(from: selectedAttachment)
}
}
@ -45,9 +45,9 @@ private struct MediaToolBar: ToolbarContent {
let data: DisplayData
var body: some ToolbarContent {
#if !targetEnvironment(macCatalyst)
DismissToolbarItem()
#endif
#if !targetEnvironment(macCatalyst)
DismissToolbarItem()
#endif
QuickLookToolbarItem(itemUrl: data.url)
AltTextToolbarItem(alt: data.description)
SavePhotoToolbarItem(url: data.url, type: data.type)
@ -75,15 +75,15 @@ private struct AltTextToolbarItem: ToolbarContent {
var body: some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) {
if let alt = alt {
if let alt {
Button {
isAlertDisplayed = true
} label: {
Text("status.image.alt-text.abbreviation")
}
.alert("status.editor.media.image-description",
isPresented: $isAlertDisplayed
) {
isPresented: $isAlertDisplayed)
{
Button("alert.button.ok", action: {})
} message: {
Text(alt)
@ -118,8 +118,8 @@ private struct SavePhotoToolbarItem: ToolbarContent, @unchecked Sendable {
} label: {
switch state {
case .unsaved: Image(systemName: "arrow.down.circle")
case .saving : ProgressView()
case .saved : Image(systemName: "checkmark.circle.fill")
case .saving: ProgressView()
case .saved: Image(systemName: "checkmark.circle.fill")
}
}
} else {
@ -205,7 +205,7 @@ private struct QuickLookToolbarItem: ToolbarContent, @unchecked Sendable {
in: .userDomainMask,
appropriateFor: nil,
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 type = attachment.supportedType else { return nil }
self.id = attachment.id
id = attachment.id
self.url = url
self.description = attachment.description
description = attachment.description
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
fileprivate let maxAllowedScale = 4.0
private let maxAllowedScale = 4.0
@MainActor
struct MediaUIZoomableContainer<Content: View>: View {
let content: Content
@State private var currentScale: CGFloat = 1.0
@State private var tapLocation: CGPoint = .zero
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func doubleTapAction(location: CGPoint) {
tapLocation = location
currentScale = currentScale == 1.0 ? maxAllowedScale : 1.0
}
var body: some View {
ZoomableScrollView(scale: $currentScale, tapLocation: $tapLocation) {
content
}
.onTapGesture(count: 2, perform: doubleTapAction)
}
fileprivate struct ZoomableScrollView<ScollContent: View>: UIViewRepresentable {
private var content: ScollContent
@Binding private var currentScale: CGFloat
@Binding private var tapLocation: CGPoint
init(scale: Binding<CGFloat>, tapLocation: Binding<CGPoint>, @ViewBuilder content: () -> ScollContent) {
_currentScale = scale
_tapLocation = tapLocation
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.backgroundColor = .clear
@ -49,24 +49,24 @@ struct MediaUIZoomableContainer<Content: View>: View {
scrollView.showsVerticalScrollIndicator = false
scrollView.clipsToBounds = false
scrollView.backgroundColor = .clear
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
hostedView.backgroundColor = .clear
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: content), scale: $currentScale)
Coordinator(hostingController: UIHostingController(rootView: content), scale: $currentScale)
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
context.coordinator.hostingController.rootView = content
if uiView.zoomScale > uiView.minimumZoomScale { // Scale out
uiView.setZoomScale(currentScale, animated: true)
} else if tapLocation != .zero { // Scale in to a specific point
@ -74,32 +74,32 @@ struct MediaUIZoomableContainer<Content: View>: View {
DispatchQueue.main.async { tapLocation = .zero }
}
}
@MainActor func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect {
let scrollViewSize = scrollView.bounds.size
let width = scrollViewSize.width / scale
let height = scrollViewSize.height / scale
let x = center.x - (width / 2.0)
let y = center.y - (height / 2.0)
return CGRect(x: x, y: y, width: width, height: height)
}
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<ScollContent>
@Binding var currentScale: CGFloat
init(hostingController: UIHostingController<ScollContent>, scale: Binding<CGFloat>) {
self.hostingController = hostingController
_currentScale = scale
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
func viewForZooming(in _: UIScrollView) -> UIView? {
hostingController.view
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
func scrollViewDidEndZooming(_: UIScrollView, with _: UIView?, atScale scale: CGFloat) {
currentScale = scale
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@ import SwiftUI
public enum Version: String, Sendable {
case v1, v2
}
public enum ClientError: Error {
case unexpectedRequest
}
@ -93,7 +93,8 @@ import SwiftUI
private func makeURL(scheme: String = "https",
endpoint: Endpoint,
forceVersion: Version? = nil,
forceServer: String? = nil) throws -> URL {
forceServer: String? = nil) throws -> URL
{
var components = URLComponents()
components.scheme = scheme
components.host = forceServer ?? server
@ -140,7 +141,7 @@ import SwiftUI
}
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?
if let response = httpResponse as? HTTPURLResponse,
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 isLanguageConfirmPresented = false
@State private var editingContainer: StatusEditorMediaContainer?
public init(mode: StatusEditorViewModel.Mode) {
@ -290,7 +290,7 @@ public struct StatusEditorView: View {
)
}
}
private func close() {
if ProcessInfo.processInfo.isMacCatalystApp {
dismissWindow()

View file

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

View file

@ -1,10 +1,10 @@
import DesignSystem
import Env
import MediaUI
import Models
import Nuke
import NukeUI
import SwiftUI
import MediaUI
@MainActor
public struct StatusRowMediaPreviewView: View {
@ -29,7 +29,7 @@ public struct StatusRowMediaPreviewView: View {
var availableWidth: CGFloat {
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
}

View file

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