diff --git a/IceCubesApp/App/Tabs/Settings/AboutView.swift b/IceCubesApp/App/Tabs/Settings/AboutView.swift index c27e04a7..b4abf434 100644 --- a/IceCubesApp/App/Tabs/Settings/AboutView.swift +++ b/IceCubesApp/App/Tabs/Settings/AboutView.swift @@ -64,7 +64,7 @@ struct AboutView: View { • [KeychainSwift](https://github.com/evgenyneu/keychain-swift) • [LRUCache](https://github.com/nicklockwood/LRUCache) - + • [Boutique](https://github.com/mergesort/Boutique) • [Nuke](https://github.com/kean/Nuke) diff --git a/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift b/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift index d80b16a3..c5fc55cc 100644 --- a/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift +++ b/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift @@ -3,9 +3,9 @@ import AppAccount import DesignSystem import Env import Models +import Network import SwiftUI import Timeline -import Network struct AccountSettingsView: View { @Environment(\.dismiss) private var dismiss diff --git a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift index ce45a6ca..910a77f6 100644 --- a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift @@ -38,12 +38,12 @@ struct DisplaySettingsView: View { } } .listRowBackground(theme.primaryBackgroundColor) - + Section("settings.display.section.display") { Picker("settings.display.font", selection: .init(get: { () -> FontState in if userPreferences.chosenFont?.fontName == "OpenDyslexic-Regular" { return FontState.openDyslexic - } else if userPreferences.chosenFont?.fontName == "AtkinsonHyperlegible-Regular" { + } else if userPreferences.chosenFont?.fontName == "AtkinsonHyperlegible-Regular" { return FontState.hyperLegible } return userPreferences.chosenFontData != nil ? FontState.custom : FontState.system @@ -79,7 +79,7 @@ struct DisplaySettingsView: View { Text(buttonStyle.description).tag(buttonStyle) } } - + Picker("settings.display.status.media-style", selection: $theme.statusDisplayStyle) { ForEach(Theme.StatusDisplayStyle.allCases, id: \.rawValue) { buttonStyle in Text(buttonStyle.description).tag(buttonStyle) diff --git a/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift b/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift index 10cf6a6f..b5e9866d 100644 --- a/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift +++ b/IceCubesApp/App/Tabs/Settings/IconSelectorView.swift @@ -45,8 +45,8 @@ struct IconSelectorView: View { static let items = [ IconSelector(title: "Official icons", icons: [.primary, .alt1, .alt2, .alt3, .alt4, .alt5, .alt6, .alt7, .alt8, - .alt9, .alt10, .alt11, .alt12, .alt13, .alt14, - .alt15, .alt16, .alt17, .alt18, .alt19, .alt25]), + .alt9, .alt10, .alt11, .alt12, .alt13, .alt14, + .alt15, .alt16, .alt17, .alt18, .alt19, .alt25]), IconSelector(title: "Icons by Albert Kinng", icons: [.alt20, .alt21, .alt22, .alt23, .alt24]), IconSelector(title: "Icons by Dan van Moll", icons: [.alt26, .alt27, .alt28]), IconSelector(title: "Icons by @te6-in (GitHub)", icons: [.alt29, .alt30, .alt31, .alt32]), diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index 5ceb074f..03b0784a 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -102,11 +102,11 @@ struct SettingsTabs: View { NavigationLink(destination: DisplaySettingsView()) { Label("settings.general.display", systemImage: "paintpalette") } - if HapticManager.shared.supportsHaptics { - NavigationLink(destination: HapticSettingsView()) { - Label("settings.general.haptic", systemImage: "waveform.path") - } + if HapticManager.shared.supportsHaptics { + NavigationLink(destination: HapticSettingsView()) { + Label("settings.general.haptic", systemImage: "waveform.path") } + } NavigationLink(destination: remoteLocalTimelinesView) { Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right") } diff --git a/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift b/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift index 18402937..ce9e97f9 100644 --- a/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift @@ -5,7 +5,7 @@ import SwiftUI struct SwipeActionsSettingsView: View { @EnvironmentObject private var theme: Theme @EnvironmentObject private var userPreferences: UserPreferences - + var body: some View { Form { Section("settings.swipeactions.status") { @@ -16,7 +16,7 @@ struct SwipeActionsSettingsView: View { } Section { ForEach(StatusAction.allCases) { action in - if (action != .none) { + if action != .none { Label(action.displayName(), systemImage: action.iconName()).tag(action) } } @@ -28,20 +28,20 @@ struct SwipeActionsSettingsView: View { } Section { ForEach(StatusAction.allCases) { action in - if (action != .none) { + if action != .none { Label(action.displayName(), systemImage: action.iconName()).tag(action) } } } } Label("settings.swipeactions.status.trailing", systemImage: "arrow.left.circle") - Picker(selection: $userPreferences.swipeActionsStatusTrailingLeft, label: makeSwipeLabel(left: true, text: "settings.swipeactions.status.trailing.left")) { + Picker(selection: $userPreferences.swipeActionsStatusTrailingLeft, label: makeSwipeLabel(left: true, text: "settings.swipeactions.status.trailing.left")) { Section { Label(StatusAction.none.displayName(), systemImage: StatusAction.none.iconName()).tag(StatusAction.none) } Section { ForEach(StatusAction.allCases) { action in - if (action != .none) { + if action != .none { Label(action.displayName(), systemImage: action.iconName()).tag(action) } } @@ -53,7 +53,7 @@ struct SwipeActionsSettingsView: View { } Section { ForEach(StatusAction.allCases) { action in - if (action != .none) { + if action != .none { Label(action.displayName(), systemImage: action.iconName()).tag(action) } } @@ -66,9 +66,9 @@ struct SwipeActionsSettingsView: View { .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) } - + private func makeSwipeLabel(left: Bool, text: LocalizedStringKey) -> some View { return Label(text, systemImage: left ? "rectangle.lefthalf.filled" : "rectangle.righthalf.filled") - .padding(.leading, 16) + .padding(.leading, 16) } } diff --git a/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift b/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift index c0abc067..a886682d 100644 --- a/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift +++ b/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift @@ -19,8 +19,8 @@ struct TimelineTab: View { @State private var didAppear: Bool = false @State private var timeline: TimelineFilter @State private var scrollToTopSignal: Int = 0 - - @AppStorage("last_timeline_filter") public var lastTimelineFilter: TimelineFilter = TimelineFilter.home + + @AppStorage("last_timeline_filter") public var lastTimelineFilter: TimelineFilter = .home private let canFilterTimeline: Bool @@ -45,10 +45,10 @@ struct TimelineTab: View { routerPath.client = client if !didAppear && canFilterTimeline { didAppear = true - if(client.isAuth) { + if client.isAuth { timeline = lastTimelineFilter } else { - timeline = .federated + timeline = .federated } } Task { @@ -58,18 +58,18 @@ struct TimelineTab: View { routerPath.presentedSheet = .addAccount } } - .onChange(of: client.isAuth, perform: { isAuth in - if(client.isAuth) { + .onChange(of: client.isAuth, perform: { _ in + if client.isAuth { timeline = lastTimelineFilter } else { - timeline = .federated + timeline = .federated } }) .onChange(of: currentAccount.account?.id, perform: { _ in - if(client.isAuth && canFilterTimeline) { + if client.isAuth, canFilterTimeline { timeline = lastTimelineFilter } else { - timeline = .federated + timeline = .federated } }) .onChange(of: $popToRootTab.wrappedValue) { popToRootTab in @@ -85,7 +85,7 @@ struct TimelineTab: View { routerPath.path = [] } .onChange(of: timeline) { timeline in - if(timeline == .home || timeline == .federated || timeline == .local) { + if timeline == .home || timeline == .federated || timeline == .local { lastTimelineFilter = timeline } } diff --git a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift index 7f150362..67968451 100644 --- a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift +++ b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift @@ -10,7 +10,7 @@ struct AccountDetailHeaderView: View { enum Constants { static let headerHeight: CGFloat = 200 } - + @EnvironmentObject private var theme: Theme @EnvironmentObject private var quickLook: QuickLook @EnvironmentObject private var routerPath: RouterPath @@ -100,7 +100,7 @@ struct AccountDetailHeaderView: View { makeCustomInfoLabel(title: "account.posts", count: account.statusesCount) } .buttonStyle(.borderless) - + Button { routerPath.navigate(to: .following(id: account.id)) } label: { @@ -118,7 +118,7 @@ struct AccountDetailHeaderView: View { ) } .buttonStyle(.borderless) - + }.offset(y: 20) } } diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index f8b4b094..aa515abc 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -36,7 +36,7 @@ public struct AccountDetailView: View { public init(account: Account) { _viewModel = StateObject(wrappedValue: .init(account: account)) } - + public var body: some View { ScrollViewReader { proxy in List { @@ -48,7 +48,7 @@ public struct AccountDetailView: View { .listRowInsets(.init()) .listRowSeparator(.hidden) .listRowBackground(theme.primaryBackgroundColor) - + Picker("", selection: $viewModel.selectedTab) { ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs, id: \.self) { tab in @@ -283,8 +283,8 @@ public struct AccountDetailView: View { ForEach(currentAccount.sortedLists) { list in NavigationLink(value: RouterDestinations.list(list: list)) { Text(list.title) - .font(.scaledHeadline) - .foregroundColor(theme.labelColor) + .font(.scaledHeadline) + .foregroundColor(theme.labelColor) } .listRowBackground(theme.primaryBackgroundColor) .contextMenu { @@ -389,7 +389,7 @@ public struct AccountDetailView: View { Label("account.action.block", systemImage: "person.crop.circle.badge.xmark") } } - + if viewModel.relationship?.muting == true { Button { Task { @@ -405,7 +405,7 @@ public struct AccountDetailView: View { } else { Menu { ForEach(MutingDurations.allCases, id: \.rawValue) { duration in - Button (duration.description) { + Button(duration.description) { Task { do { viewModel.relationship = try await client.post(endpoint: Accounts.mute(id: account.id, json: MuteData(duration: duration.rawValue))) diff --git a/Packages/Account/Sources/Account/MutingDurations.swift b/Packages/Account/Sources/Account/MutingDurations.swift index 49056bfa..090b7736 100644 --- a/Packages/Account/Sources/Account/MutingDurations.swift +++ b/Packages/Account/Sources/Account/MutingDurations.swift @@ -3,13 +3,13 @@ import SwiftUI enum MutingDurations: Int, CaseIterable { case infinite = 0 case fiveMinutes = 300 - case thirtyMinutes = 1_800 - case oneHour = 3_600 - case sixHours = 21_600 - case oneDay = 86_400 + case thirtyMinutes = 1800 + case oneHour = 3600 + case sixHours = 21600 + case oneDay = 86400 case threeDays = 259_200 case sevenDays = 604_800 - + public var description: LocalizedStringKey { switch self { case .infinite: diff --git a/Packages/AppAccount/Sources/AppAccount/AppAccountViewModel.swift b/Packages/AppAccount/Sources/AppAccount/AppAccountViewModel.swift index 43f5cae9..326c682b 100644 --- a/Packages/AppAccount/Sources/AppAccount/AppAccountViewModel.swift +++ b/Packages/AppAccount/Sources/AppAccount/AppAccountViewModel.swift @@ -1,19 +1,19 @@ +import DesignSystem import Models import Network import SwiftUI -import DesignSystem @MainActor public class AppAccountViewModel: ObservableObject { private static var avatarsCache: [String: UIImage] = [:] - + var appAccount: AppAccount let client: Client let isCompact: Bool @Published var account: Account? @Published var roundedAvatar: UIImage? - + var acct: String { if let acct = appAccount.accountName { return acct @@ -35,17 +35,18 @@ public class AppAccountViewModel: ObservableObject { appAccount.accountName = "\(account.acct)@\(appAccount.server)" try appAccount.save() } - + if let account { if let image = Self.avatarsCache[account.id] { - self.roundedAvatar = image + roundedAvatar = image } else if let (data, _) = try? await URLSession.shared.data(from: account.avatar), - let image = UIImage(data: data)?.roundedImage { - self.roundedAvatar = image + let image = UIImage(data: data)?.roundedImage + { + roundedAvatar = image Self.avatarsCache[account.id] = image } } - - } catch { } + + } catch {} } } diff --git a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailViewModel.swift b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailViewModel.swift index dab95064..662f7b9e 100644 --- a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailViewModel.swift +++ b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailViewModel.swift @@ -17,7 +17,7 @@ class ConversationDetailViewModel: ObservableObject { init(conversation: Conversation) { self.conversation = conversation - messages = conversation.lastStatus != nil ? [conversation.lastStatus!] : [] + messages = conversation.lastStatus != nil ? [conversation.lastStatus!] : [] } func fetchMessages() async { diff --git a/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift b/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift index c534ab4c..2f31d472 100644 --- a/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift +++ b/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift @@ -60,8 +60,8 @@ struct ConversationMessageView: View { .padding(.leading, isOwnMessage ? 24 : 0) .padding(.trailing, isOwnMessage ? 0 : 24) } - - if message.id == String(conversation.lastStatus?.id ?? "") { + + if message.id == String(conversation.lastStatus?.id ?? "") { HStack { if isOwnMessage { Spacer() diff --git a/Packages/DesignSystem/Sources/DesignSystem/UIImage.swift b/Packages/DesignSystem/Sources/DesignSystem/UIImage.swift index 3eb8d996..6dac08b9 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/UIImage.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/UIImage.swift @@ -1,16 +1,15 @@ import UIKit -extension UIImage{ - public var roundedImage: UIImage? { - let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size) - UIGraphicsBeginImageContextWithOptions(self.size, false, 1) +public extension UIImage { + var roundedImage: UIImage? { + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) + UIGraphicsBeginImageContextWithOptions(size, false, 1) defer { UIGraphicsEndImageContext() } UIBezierPath( roundedRect: rect, - cornerRadius: self.size.height + cornerRadius: size.height ).addClip() - self.draw(in: rect) + draw(in: rect) return UIGraphicsGetImageFromCurrentImageContext() } } - diff --git a/Packages/Env/Sources/Env/CurrentInstance.swift b/Packages/Env/Sources/Env/CurrentInstance.swift index 443419d8..558f6346 100644 --- a/Packages/Env/Sources/Env/CurrentInstance.swift +++ b/Packages/Env/Sources/Env/CurrentInstance.swift @@ -9,7 +9,7 @@ public class CurrentInstance: ObservableObject { private var client: Client? public static let shared = CurrentInstance() - + private var version: Float { if let stringVersion = instance?.version { if stringVersion.utf8.count > 2 { @@ -20,7 +20,6 @@ public class CurrentInstance: ObservableObject { } return 0 } - public var isFiltersSupported: Bool { version >= 4 @@ -29,7 +28,7 @@ public class CurrentInstance: ObservableObject { public var isEditSupported: Bool { version >= 4 } - + public var isEditAltTextSupported: Bool { version >= 4.1 } diff --git a/Packages/Env/Sources/Env/StatusAction.swift b/Packages/Env/Sources/Env/StatusAction.swift index b4cc07b5..b277b63b 100644 --- a/Packages/Env/Sources/Env/StatusAction.swift +++ b/Packages/Env/Sources/Env/StatusAction.swift @@ -1,12 +1,12 @@ import SwiftUI -public enum StatusAction : String, CaseIterable, Identifiable { +public enum StatusAction: String, CaseIterable, Identifiable { public var id: String { "\(rawValue)" } - + case none, reply, boost, favorite, bookmark, quote - + public func displayName(isReblogged: Bool = false, isFavorited: Bool = false, isBookmarked: Bool = false) -> LocalizedStringKey { switch self { case .none: @@ -23,7 +23,7 @@ public enum StatusAction : String, CaseIterable, Identifiable { return isBookmarked ? "status.action.unbookmark" : "settings.swipeactions.status.action.bookmark" } } - + public func iconName(isReblogged: Bool = false, isFavorited: Bool = false, isBookmarked: Bool = false) -> String { switch self { case .none: @@ -40,7 +40,7 @@ public enum StatusAction : String, CaseIterable, Identifiable { return isBookmarked ? "bookmark.fill" : "bookmark" } } - + public func color(themeTintColor: Color) -> Color { switch self { case .none: diff --git a/Packages/Env/Sources/Env/UserPreferences.swift b/Packages/Env/Sources/Env/UserPreferences.swift index 6b35eccd..2420edf0 100644 --- a/Packages/Env/Sources/Env/UserPreferences.swift +++ b/Packages/Env/Sources/Env/UserPreferences.swift @@ -31,13 +31,13 @@ public class UserPreferences: ObservableObject { @AppStorage("suppress_dupe_reblogs") public var suppressDupeReblogs: Bool = false @AppStorage("inAppBrowserReaderView") public var inAppBrowserReaderView = false - + @AppStorage("haptic_tab") public var hapticTabSelectionEnabled = true @AppStorage("haptic_timeline") public var hapticTimelineEnabled = true @AppStorage("haptic_button_press") public var hapticButtonPressEnabled = true - + @AppStorage("show_tab_label_iphone") public var showiPhoneTabLabel = true - + @AppStorage("show_second_column_ipad") public var showiPadSecondaryColumn = true @AppStorage("swipeactions-status-trailing-right") public var swipeActionsStatusTrailingRight = StatusAction.favorite diff --git a/Packages/Models/Sources/Models/Alias/HTMLString.swift b/Packages/Models/Sources/Models/Alias/HTMLString.swift index cab01664..c5e27d9a 100644 --- a/Packages/Models/Sources/Models/Alias/HTMLString.swift +++ b/Packages/Models/Sources/Models/Alias/HTMLString.swift @@ -2,7 +2,7 @@ import Foundation import SwiftSoup import SwiftUI -fileprivate enum CodingKeys: CodingKey { +private enum CodingKeys: CodingKey { case htmlValue, asMarkdown, asRawText, statusesURLs } @@ -11,12 +11,12 @@ public struct HTMLString: Codable, Equatable, Hashable { public var asMarkdown: String = "" public var asRawText: String = "" public var statusesURLs = [URL]() - + public var asSafeMarkdownAttributedString: AttributedString = .init() private var main_regex: NSRegularExpression? private var underscore_regex: NSRegularExpression? public init(from decoder: Decoder) { - var alreadyDecoded: Bool = false + var alreadyDecoded = false do { let container = try decoder.singleValueContainer() htmlValue = try container.decode(String.self) @@ -67,7 +67,7 @@ public struct HTMLString: Codable, Equatable, Hashable { asRawText = htmlValue } } - + do { let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true, interpretedSyntax: .inlineOnlyPreservingWhitespace) @@ -77,12 +77,12 @@ public struct HTMLString: Codable, Equatable, Hashable { } } - public init(stringValue: String, parseMarkdown:Bool = false) { + public init(stringValue: String, parseMarkdown: Bool = false) { htmlValue = stringValue asMarkdown = stringValue asRawText = stringValue statusesURLs = [] - + if parseMarkdown { do { let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true, @@ -91,8 +91,7 @@ public struct HTMLString: Codable, Equatable, Hashable { } catch { asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue) } - } - else { + } else { asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue) } } diff --git a/Packages/Models/Sources/Models/Alias/ServerDate.swift b/Packages/Models/Sources/Models/Alias/ServerDate.swift index 06f48e9a..673ac5c4 100644 --- a/Packages/Models/Sources/Models/Alias/ServerDate.swift +++ b/Packages/Models/Sources/Models/Alias/ServerDate.swift @@ -1,12 +1,12 @@ import Foundation -fileprivate enum CodingKeys: CodingKey { +private enum CodingKeys: CodingKey { case asDate } public struct ServerDate: Codable, Hashable, Equatable { public let asDate: Date - + public var relativeFormatted: String { Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date()) } @@ -14,9 +14,9 @@ public struct ServerDate: Codable, Hashable, Equatable { public var shortDateFormatted: String { Self.createdAtShortDateFormatted.string(from: asDate) } - + private static let calendar = Calendar(identifier: .gregorian) - + private static var createdAtDateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.calendar = .init(identifier: .iso8601) @@ -24,24 +24,24 @@ public struct ServerDate: Codable, Hashable, Equatable { dateFormatter.timeZone = .init(abbreviation: "UTC") return dateFormatter }() - + private static var createdAtRelativeFormatter: RelativeDateTimeFormatter = { let dateFormatter = RelativeDateTimeFormatter() dateFormatter.unitsStyle = .short return dateFormatter }() - + private static var createdAtShortDateFormatted: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .none return dateFormatter }() - + public init() { - asDate = Date()-100 + asDate = Date() - 100 } - + public init(from decoder: Decoder) throws { do { // Decode from server @@ -54,7 +54,7 @@ public struct ServerDate: Codable, Hashable, Equatable { asDate = try container.decode(Date.self, forKey: .asDate) } } - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(asDate, forKey: .asDate) diff --git a/Packages/Models/Sources/Models/Status.swift b/Packages/Models/Sources/Models/Status.swift index dfa83abd..3ea51f0a 100644 --- a/Packages/Models/Sources/Models/Status.swift +++ b/Packages/Models/Sources/Models/Status.swift @@ -54,14 +54,12 @@ public protocol AnyStatus { var language: String? { get } } -extension AnyStatus { - public var viewId: String { - get { - if let editedAt { - return "\(id)\(editedAt.asDate.description)" - } - return id +public extension AnyStatus { + var viewId: String { + if let editedAt { + return "\(id)\(editedAt.asDate.description)" } + return id } } @@ -107,41 +105,40 @@ public struct Status: AnyStatus, Codable, Identifiable, Equatable, Hashable, Sta public let sensitive: Bool public let language: String? - public static func placeholder(forSettings:Bool = false, language:String? = nil) -> Status { + public static func placeholder(forSettings: Bool = false, language: String? = nil) -> Status { .init(id: UUID().uuidString, - content: .init(stringValue: "Lorem ipsum [#dolor](#) sit amet\nconsectetur [@adipiscing](#) elit\nAsed do eiusmod tempor incididunt ut labore.", parseMarkdown: forSettings), + content: .init(stringValue: "Lorem ipsum [#dolor](#) sit amet\nconsectetur [@adipiscing](#) elit\nAsed do eiusmod tempor incididunt ut labore.", parseMarkdown: forSettings), - account: .placeholder(), - createdAt: ServerDate(), - editedAt: nil, - reblog: nil, - mediaAttachments: [], - mentions: [], - repliesCount: 0, - reblogsCount: 0, - favouritesCount: 0, - card: nil, - favourited: false, - reblogged: false, - pinned: false, - bookmarked: false, - emojis: [], - url: "https://example.com", - application: nil, - inReplyToAccountId: nil, - visibility: .pub, - poll: nil, - spoilerText: .init(stringValue: ""), - filtered: [], - sensitive: false, - language: language) + account: .placeholder(), + createdAt: ServerDate(), + editedAt: nil, + reblog: nil, + mediaAttachments: [], + mentions: [], + repliesCount: 0, + reblogsCount: 0, + favouritesCount: 0, + card: nil, + favourited: false, + reblogged: false, + pinned: false, + bookmarked: false, + emojis: [], + url: "https://example.com", + application: nil, + inReplyToAccountId: nil, + visibility: .pub, + poll: nil, + spoilerText: .init(stringValue: ""), + filtered: [], + sensitive: false, + language: language) } - public static func placeholders() -> [Status] { [.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()] } - + public var reblogAsAsStatus: Status? { if let reblog { return .init(id: reblog.id, diff --git a/Packages/Models/Sources/Models/StatusContext.swift b/Packages/Models/Sources/Models/StatusContext.swift index 08681469..0fa4a6c0 100644 --- a/Packages/Models/Sources/Models/StatusContext.swift +++ b/Packages/Models/Sources/Models/StatusContext.swift @@ -3,7 +3,7 @@ import Foundation public struct StatusContext: Decodable { public let ancestors: [Status] public let descendants: [Status] - + public static func empty() -> StatusContext { .init(ancestors: [], descendants: []) } diff --git a/Packages/Models/Sources/Models/StatusTranslation.swift b/Packages/Models/Sources/Models/StatusTranslation.swift index 77c7d41b..41939ba5 100644 --- a/Packages/Models/Sources/Models/StatusTranslation.swift +++ b/Packages/Models/Sources/Models/StatusTranslation.swift @@ -4,7 +4,7 @@ public struct StatusTranslation: Decodable { public let content: HTMLString public let detectedSourceLanguage: String public let provider: String - + public init(content: String, detectedSourceLanguage: String, provider: String) { self.content = .init(stringValue: content) self.detectedSourceLanguage = detectedSourceLanguage diff --git a/Packages/Network/Sources/Network/DeepLClient.swift b/Packages/Network/Sources/Network/DeepLClient.swift index 4df43ec3..df75398b 100644 --- a/Packages/Network/Sources/Network/DeepLClient.swift +++ b/Packages/Network/Sources/Network/DeepLClient.swift @@ -5,7 +5,7 @@ public struct DeepLClient { public enum DeepLError: Error { case notFound } - + private let endpoint = "https://api.deepl.com/v2/translate" private var APIKey: String { diff --git a/Packages/Network/Sources/Network/Endpoint/Accounts.swift b/Packages/Network/Sources/Network/Endpoint/Accounts.swift index 071b7837..10e4cc30 100644 --- a/Packages/Network/Sources/Network/Endpoint/Accounts.swift +++ b/Packages/Network/Sources/Network/Endpoint/Accounts.swift @@ -34,7 +34,7 @@ public enum Accounts: Endpoint { case unblock(id: String) case mute(id: String, json: MuteData) case unmute(id: String) - + public func path() -> String { switch self { case let .accounts(id): @@ -81,7 +81,7 @@ public enum Accounts: Endpoint { return "accounts/\(id)/unmute" } } - + public func queryItems() -> [URLQueryItem]? { switch self { case let .statuses(_, sinceId, tag, onlyMedia, excludeReplies, pinned): @@ -138,7 +138,7 @@ public enum Accounts: Endpoint { return nil } } - + public var jsonValue: Encodable? { switch self { case let .mute(_, json): @@ -151,7 +151,7 @@ public enum Accounts: Endpoint { public struct MuteData: Encodable { public let duration: Int - + public init(duration: Int) { self.duration = duration } diff --git a/Packages/Network/Sources/Network/Endpoint/Statuses.swift b/Packages/Network/Sources/Network/Endpoint/Statuses.swift index 8b670da4..f08c16bb 100644 --- a/Packages/Network/Sources/Network/Endpoint/Statuses.swift +++ b/Packages/Network/Sources/Network/Endpoint/Statuses.swift @@ -105,14 +105,14 @@ public struct StatusData: Encodable { self.expires_in = expires_in } } - + public struct MediaAttribute: Encodable { public let id: String public let description: String? public let thumbnail: String? public let focus: String? - - public init(id: String, description: String?, thumbnail: String?, focus: String?) { + + public init(id: String, description: String?, thumbnail: String?, focus: String?) { self.id = id self.description = description self.thumbnail = thumbnail diff --git a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift index 0f9efc00..d3eaf579 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift @@ -98,8 +98,8 @@ public struct NotificationsListView: View { EmptyView(iconName: "bell.slash", title: "notifications.empty.title", message: "notifications.empty.message") - .listRowBackground(theme.primaryBackgroundColor) - .listSectionSeparator(.hidden) + .listRowBackground(theme.primaryBackgroundColor) + .listSectionSeparator(.hidden) } else { ForEach(notifications) { notification in NotificationRowView(notification: notification) diff --git a/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift b/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift index dac71463..70653901 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift @@ -15,7 +15,7 @@ class NotificationsViewModel: ObservableObject { case display(notifications: [ConsolidatedNotification], nextPageState: State.PagingState) case error(error: Error) } - + enum Constants { static let notificationLimit: Int = 30 } @@ -32,6 +32,7 @@ class NotificationsViewModel: ObservableObject { } } } + var currentAccount: CurrentAccount? @Published var state: State = .loading @@ -92,7 +93,7 @@ class NotificationsViewModel: ObservableObject { state = .error(error: error) } } - + private func fetchNewPages(minId: String, maxPages: Int) async -> [Models.Notification] { guard let client else { return [] } var pagesLoaded = 0 @@ -100,10 +101,10 @@ class NotificationsViewModel: ObservableObject { var latestMinId = minId do { while let newNotifications: [Models.Notification] = - try await client.get(endpoint: Notifications.notifications(minId: latestMinId, - maxId: nil, - types: queryTypes, - limit: Constants.notificationLimit)), + try await client.get(endpoint: Notifications.notifications(minId: latestMinId, + maxId: nil, + types: queryTypes, + limit: Constants.notificationLimit)), !newNotifications.isEmpty, pagesLoaded < maxPages { diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift index 7805e2c3..614059cf 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift @@ -4,7 +4,6 @@ import Models import Network import Shimmer import SwiftUI -import DesignSystem public struct StatusDetailView: View { @EnvironmentObject private var theme: Theme @@ -16,19 +15,19 @@ public struct StatusDetailView: View { @StateObject private var viewModel: StatusDetailViewModel @State private var isLoaded: Bool = false @State private var statusHeight: CGFloat = 0 - + public init(statusId: String) { _viewModel = StateObject(wrappedValue: .init(statusId: statusId)) } - + public init(status: Status) { _viewModel = StateObject(wrappedValue: .init(status: status)) } - + public init(remoteStatusURL: URL) { _viewModel = StateObject(wrappedValue: .init(remoteStatusURL: remoteStatusURL)) } - + public var body: some View { GeometryReader { reader in ScrollViewReader { proxy in @@ -36,38 +35,38 @@ public struct StatusDetailView: View { if isLoaded { topPaddingView } - + switch viewModel.state { case .loading: loadingDetailView - + case let .display(status, context, date): if !context.ancestors.isEmpty { ForEach(context.ancestors) { ancestor in StatusRowView(viewModel: .init(status: ancestor, isCompact: false)) } } - + makeCurrentStatusView(status: status) .id(date) - + if !context.descendants.isEmpty { ForEach(context.descendants) { descendant in StatusRowView(viewModel: .init(status: descendant, isCompact: false)) } } - + if !isLoaded { loadingContextView } - + Rectangle() .foregroundColor(theme.secondaryBackgroundColor) .frame(minHeight: reader.frame(in: .local).size.height - statusHeight) .listRowSeparator(.hidden) .listRowBackground(theme.secondaryBackgroundColor) .listRowInsets(.init()) - + case .error: errorView } @@ -87,7 +86,7 @@ public struct StatusDetailView: View { viewModel.client = client let result = await viewModel.fetch() isLoaded = true - + if !result { if let url = viewModel.remoteStatusURL { openURL(url) @@ -106,22 +105,22 @@ public struct StatusDetailView: View { .navigationTitle(viewModel.title) .navigationBarTitleDisplayMode(.inline) } - + private func makeCurrentStatusView(status: Status) -> some View { StatusRowView(viewModel: .init(status: status, isCompact: false, isFocused: true)) - .overlay { - GeometryReader { reader in - VStack{} - .onAppear { - statusHeight = reader.size.height + .overlay { + GeometryReader { reader in + VStack {} + .onAppear { + statusHeight = reader.size.height + } } } - } - .id(status.id) + .id(status.id) } - + private var errorView: some View { ErrorView(title: "status.error.title", message: "status.error.message", @@ -133,14 +132,14 @@ public struct StatusDetailView: View { .listRowBackground(theme.primaryBackgroundColor) .listRowSeparator(.hidden) } - + private var loadingDetailView: some View { ForEach(Status.placeholders()) { status in StatusRowView(viewModel: .init(status: status, isCompact: false)) .redacted(reason: .placeholder) } } - + private var loadingContextView: some View { HStack { Spacer() @@ -152,7 +151,7 @@ public struct StatusDetailView: View { .listRowBackground(theme.secondaryBackgroundColor) .listRowInsets(.init()) } - + private var topPaddingView: some View { HStack { EmptyView() } .listRowBackground(theme.primaryBackgroundColor) diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift b/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift index 983548c0..c79147ab 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift @@ -23,7 +23,7 @@ class StatusDetailViewModel: ObservableObject { self.statusId = statusId remoteStatusURL = nil } - + init(status: Status) { state = .display(status: status, context: .empty(), date: Date()) title = "status.post-from-\(status.account.displayNameWithoutEmojis)" @@ -78,7 +78,7 @@ class StatusDetailViewModel: ObservableObject { state = .display(status: data.status, context: data.context, date: Date()) } } else { - state = .display(status: data.status, context: data.context,date: Date()) + state = .display(status: data.status, context: data.context, date: Date()) scrollToId = statusId } } catch { diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift index 22c645c5..88bbf089 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift @@ -33,7 +33,7 @@ struct StatusEditorAccessoryView: View { Image(systemName: "photo.fill.on.rectangle.fill") } } - .accessibilityLabel("accessibility.editor.button.attach-photo") + .accessibilityLabel("accessibility.editor.button.attach-photo") .disabled(viewModel.showPoll) Button { @@ -85,7 +85,7 @@ struct StatusEditorAccessoryView: View { } .accessibilityLabel("accessibility.editor.button.language") - if preferences.isOpenAIEnabled { + if preferences.isOpenAIEnabled { AIMenu.disabled(!viewModel.canPost) } } diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift index 15e5ec86..fd4ab22d 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift @@ -34,7 +34,7 @@ enum StatusEditorUTTypeSupported: String, CaseIterable { return false } } - + var isGif: Bool { switch self { case .gif, .gif2: @@ -54,12 +54,14 @@ enum StatusEditorUTTypeSupported: String, CaseIterable { if self == .jpeg || self == .png || self == .tiff || self == .image { if let imageURL = result as? URL, let data = try? Data(contentsOf: imageURL), - let image = UIImage(data: data) { + let image = UIImage(data: data) + { return image } else if let data = result as? Data, - let image = UIImage(data: data) { + let image = UIImage(data: data) + { return image - } else if let transferable = await getImageTansferable(item: item){ + } else if let transferable = await getImageTansferable(item: item) { return transferable } } @@ -86,7 +88,7 @@ enum StatusEditorUTTypeSupported: String, CaseIterable { } } } - + private func getGifTransferable(item: NSItemProvider) async -> GifFileTranseferable? { return await withCheckedContinuation { continuation in _ = item.loadTransferable(type: GifFileTranseferable.self) { result in @@ -99,7 +101,7 @@ enum StatusEditorUTTypeSupported: String, CaseIterable { } } } - + private func getImageTansferable(item: NSItemProvider) async -> ImageFileTranseferable? { return await withCheckedContinuation { continuation in _ = item.loadTransferable(type: ImageFileTranseferable.self) { result in @@ -139,7 +141,7 @@ struct MovieFileTranseferable: Transferable { FileRepresentation(contentType: .movie) { movie in SentTransferredFile(movie.url) } importing: { received in - return Self(url: localURLFor(received: received)) + Self(url: localURLFor(received: received)) } } } @@ -155,7 +157,7 @@ struct ImageFileTranseferable: Transferable { FileRepresentation(contentType: .image) { image in SentTransferredFile(image.url) } importing: { received in - return Self(url: localURLFor(received: received)) + Self(url: localURLFor(received: received)) } } } @@ -171,12 +173,12 @@ struct GifFileTranseferable: Transferable { FileRepresentation(contentType: .gif) { gif in SentTransferredFile(gif.url) } importing: { received in - return Self(url: localURLFor(received: received)) + Self(url: localURLFor(received: received)) } } } -fileprivate func localURLFor(received: ReceivedTransferredFile) -> URL { +private func localURLFor(received: ReceivedTransferredFile) -> URL { let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(received.file.pathExtension)") try? FileManager.default.copyItem(at: received.file, to: copy) return copy diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift index 7756bd14..93ba7c0a 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift @@ -37,11 +37,11 @@ public struct StatusEditorView: View { .padding(.horizontal, .layoutPadding) TextView($viewModel.statusText, getTextView: { textView in - viewModel.textView = textView - }) - .placeholder(String(localized: "status.editor.text.placeholder")) - .setKeyboardType(preferences.isSocialKeyboardEnabled ? .twitter : .default) - .padding(.horizontal, .layoutPadding) + viewModel.textView = textView + }) + .placeholder(String(localized: "status.editor.text.placeholder")) + .setKeyboardType(preferences.isSocialKeyboardEnabled ? .twitter : .default) + .padding(.horizontal, .layoutPadding) StatusEditorMediaView(viewModel: viewModel) if let status = viewModel.embeddedStatus { StatusEmbeddedView(status: status) diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift b/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift index 37419954..5672cb3e 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift @@ -20,7 +20,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject { textView?.pasteDelegate = self } } - + var selectedRange: NSRange { get { guard let textView else { @@ -32,16 +32,14 @@ public class StatusEditorViewModel: NSObject, ObservableObject { textView?.selectedRange = newValue } } - + var markedTextRange: UITextRange? { - get { - guard let textView else { - return nil - } - return textView.markedTextRange + guard let textView else { + return nil } + return textView.markedTextRange } - + @Published var statusText = NSMutableAttributedString(string: "") { didSet { let range = selectedRange @@ -372,7 +370,8 @@ public class StatusEditorViewModel: NSObject, ObservableObject { mediaAttachment: nil, error: nil)) } else if var content = content as? ImageFileTranseferable, - let image = content.image { + let image = content.image + { mediasImages.append(.init(image: image, movieTransferable: nil, gifTransferable: nil, @@ -507,7 +506,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject { for media in selectedMedias { print(media.supportedContentTypes) var file: (any Transferable)? - + if file == nil { file = try? await media.loadTransferable(type: GifFileTranseferable.self) } @@ -682,6 +681,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject { } // MARK: - Custom emojis + func fetchCustomEmojis() async { guard let client else { return } do { @@ -690,7 +690,8 @@ public class StatusEditorViewModel: NSObject, ObservableObject { } } -//MARK: - DropDelegate +// MARK: - DropDelegate + extension StatusEditorViewModel: DropDelegate { public func performDrop(info: DropInfo) -> Bool { let item = info.itemProviders(for: StatusEditorUTTypeSupported.types()) @@ -700,17 +701,20 @@ extension StatusEditorViewModel: DropDelegate { } // MARK: - UITextPasteDelegate + extension StatusEditorViewModel: UITextPasteDelegate { public func textPasteConfigurationSupporting( - _ textPasteConfigurationSupporting: UITextPasteConfigurationSupporting, - transform item: UITextPasteItem) { - if !item.itemProvider.registeredContentTypes(conformingTo: .image).isEmpty || - !item.itemProvider.registeredContentTypes(conformingTo: .video).isEmpty || - !item.itemProvider.registeredContentTypes(conformingTo: .gif).isEmpty { - processItemsProvider(items: [item.itemProvider]) - item.setNoResult() - } else { - item.setDefaultResult() - } + _: UITextPasteConfigurationSupporting, + transform item: UITextPasteItem + ) { + if !item.itemProvider.registeredContentTypes(conformingTo: .image).isEmpty || + !item.itemProvider.registeredContentTypes(conformingTo: .video).isEmpty || + !item.itemProvider.registeredContentTypes(conformingTo: .gif).isEmpty + { + processItemsProvider(items: [item.itemProvider]) + item.setNoResult() + } else { + item.setDefaultResult() } + } } diff --git a/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift b/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift index 76be8572..a2986bee 100644 --- a/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift +++ b/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift @@ -1,38 +1,37 @@ -import SwiftUI import DesignSystem +import SwiftUI extension TextView.Representable { final class Coordinator: NSObject, UITextViewDelegate { - internal let textView: UIKitTextView - + private var originalText: NSMutableAttributedString = .init() private var text: Binding private var calculatedHeight: Binding - + var didBecomeFirstResponder = false - + var getTextView: ((UITextView) -> Void)? - + init(text: Binding, calculatedHeight: Binding, - getTextView: ((UITextView) -> Void)? - ) { + getTextView: ((UITextView) -> Void)?) + { textView = UIKitTextView() textView.backgroundColor = .clear textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) textView.isScrollEnabled = false textView.textContainer.lineFragmentPadding = 0 textView.textContainerInset = .zero - + self.text = text self.calculatedHeight = calculatedHeight self.getTextView = getTextView - + super.init() - + textView.delegate = self - + textView.font = Font.scaledBodyUIFont textView.adjustsFontForContentSizeCategory = true textView.autocapitalizationType = .sentences @@ -43,46 +42,43 @@ extension TextView.Representable { textView.allowsEditingTextAttributes = false textView.returnKeyType = .default textView.allowsEditingTextAttributes = true - + self.getTextView?(textView) } - - func textViewDidBeginEditing(_ textView: UITextView) { + + func textViewDidBeginEditing(_: UITextView) { originalText = text.wrappedValue DispatchQueue.main.async { self.recalculateHeight() } } - + func textViewDidChange(_ textView: UITextView) { DispatchQueue.main.async { self.text.wrappedValue = NSMutableAttributedString(attributedString: textView.attributedText) self.recalculateHeight() } } - - func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + + func textView(_: UITextView, shouldChangeTextIn _: NSRange, replacementText _: String) -> Bool { return true } } - } extension TextView.Representable.Coordinator { - func update(representable: TextView.Representable) { textView.keyboardType = representable.keyboard recalculateHeight() textView.setNeedsDisplay() } - + private func recalculateHeight() { let newSize = textView.sizeThatFits(CGSize(width: textView.frame.width, height: .greatestFiniteMagnitude)) guard calculatedHeight.wrappedValue != newSize.height else { return } - + DispatchQueue.main.async { // call in next render cycle. self.calculatedHeight.wrappedValue = newSize.height } } - } diff --git a/Packages/Status/Sources/Status/Editor/UITextView/Modifiers.swift b/Packages/Status/Sources/Status/Editor/UITextView/Modifiers.swift index 3e2532ef..b2ca3763 100644 --- a/Packages/Status/Sources/Status/Editor/UITextView/Modifiers.swift +++ b/Packages/Status/Sources/Status/Editor/UITextView/Modifiers.swift @@ -1,13 +1,12 @@ import SwiftUI public extension TextView { - /// Specify a placeholder text /// - Parameter placeholder: The placeholder text func placeholder(_ placeholder: String) -> TextView { self.placeholder(placeholder) { $0 } } - + /// Specify a placeholder with the specified configuration /// /// Example: @@ -22,18 +21,17 @@ public extension TextView { view.placeholderView = AnyView(configure(text)) return view } - + /// Specify a custom placeholder view func placeholder(_ placeholder: V) -> TextView { var view = self view.placeholderView = AnyView(placeholder) return view } - + func setKeyboardType(_ keyboardType: UIKeyboardType) -> TextView { var view = self view.keyboard = keyboardType return view } - } diff --git a/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift b/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift index a878a90a..106878d2 100644 --- a/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift +++ b/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift @@ -2,25 +2,24 @@ import SwiftUI extension TextView { struct Representable: UIViewRepresentable { - @Binding var text: NSMutableAttributedString @Binding var calculatedHeight: CGFloat - + let keyboard: UIKeyboardType var getTextView: ((UITextView) -> Void)? - + func makeUIView(context: Context) -> UIKitTextView { context.coordinator.textView } - - func updateUIView(_ view: UIKitTextView, context: Context) { + + func updateUIView(_: UIKitTextView, context: Context) { context.coordinator.update(representable: self) if !context.coordinator.didBecomeFirstResponder { context.coordinator.textView.becomeFirstResponder() context.coordinator.didBecomeFirstResponder = true } } - + @discardableResult func makeCoordinator() -> Coordinator { Coordinator( text: $text, @@ -28,7 +27,5 @@ extension TextView { getTextView: getTextView ) } - } - } diff --git a/Packages/Status/Sources/Status/Editor/UITextView/TextView.swift b/Packages/Status/Sources/Status/Editor/UITextView/TextView.swift index 94de0f8f..2cf53880 100644 --- a/Packages/Status/Sources/Status/Editor/UITextView/TextView.swift +++ b/Packages/Status/Sources/Status/Editor/UITextView/TextView.swift @@ -1,36 +1,35 @@ -import SwiftUI import DesignSystem +import SwiftUI /// A SwiftUI TextView implementation that supports both scrolling and auto-sizing layouts public struct TextView: View { - @Environment(\.layoutDirection) private var layoutDirection - + @Binding private var text: NSMutableAttributedString @Binding private var isEmpty: Bool - + @State private var calculatedHeight: CGFloat = 44 - + private var getTextView: ((UITextView) -> Void)? - + var placeholderView: AnyView? var keyboard: UIKeyboardType = .default - + /// Makes a new TextView that supports `NSAttributedString` /// - Parameters: /// - text: A binding to the attributed text public init(_ text: Binding, - getTextView: ((UITextView) -> Void)? = nil - ) { + getTextView: ((UITextView) -> Void)? = nil) + { _text = text _isEmpty = Binding( get: { text.wrappedValue.string.isEmpty }, set: { _ in } ) - + self.getTextView = getTextView } - + public var body: some View { Representable( text: $text, @@ -53,19 +52,16 @@ public struct TextView: View { alignment: .topLeading ) } - } final class UIKitTextView: UITextView { - override var keyCommands: [UIKeyCommand]? { return (super.keyCommands ?? []) + [ - UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(escape(_:))) + UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(escape(_:))), ] } - - @objc private func escape(_ sender: Any) { + + @objc private func escape(_: Any) { resignFirstResponder() } - } diff --git a/Packages/Status/Sources/Status/Poll/StatusPollView.swift b/Packages/Status/Sources/Status/Poll/StatusPollView.swift index d13d840c..283a7566 100644 --- a/Packages/Status/Sources/Status/Poll/StatusPollView.swift +++ b/Packages/Status/Sources/Status/Poll/StatusPollView.swift @@ -37,7 +37,8 @@ public struct StatusPollView: View { private func isSelected(option: Poll.Option) -> Bool { if let optionIndex = viewModel.poll.options.firstIndex(where: { $0.id == option.id }), - let _ = viewModel.votes.firstIndex(of: optionIndex) { + let _ = viewModel.votes.firstIndex(of: optionIndex) + { return true } return false @@ -62,7 +63,7 @@ public struct StatusPollView: View { return Image(systemName: imageName) .foregroundColor(theme.labelColor) } - + public var body: some View { VStack(alignment: .leading) { ForEach(viewModel.poll.options) { option in diff --git a/Packages/Status/Sources/Status/Poll/StatusPollViewModel.swift b/Packages/Status/Sources/Status/Poll/StatusPollViewModel.swift index 5414b1b2..63acaf0a 100644 --- a/Packages/Status/Sources/Status/Poll/StatusPollViewModel.swift +++ b/Packages/Status/Sources/Status/Poll/StatusPollViewModel.swift @@ -38,7 +38,7 @@ public class StatusPollViewModel: ObservableObject { print(error) } } - + public func handleSelection(_ pollIndex: Int) { if poll.multiple { if let voterIndex = votes.firstIndex(of: pollIndex) { diff --git a/Packages/Status/Sources/Status/Row/StatusActionsView.swift b/Packages/Status/Sources/Status/Row/StatusActionsView.swift index 9d265b6c..64a92ebd 100644 --- a/Packages/Status/Sources/Status/Row/StatusActionsView.swift +++ b/Packages/Status/Sources/Status/Row/StatusActionsView.swift @@ -94,7 +94,7 @@ struct StatusActionsView: View { } } } - + private func handleAction(action: Actions) { Task { HapticManager.shared.fireHaptic(of: .notification(.success)) diff --git a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift index 745328fd..94d0e2f6 100644 --- a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift @@ -163,7 +163,7 @@ public struct StatusMediaPreviewView: View { .frame(width: newSize.width, height: newSize.height) } } - + case .gifv, .video, .audio: if let url = attachment.url { VideoPlayerView(viewModel: .init(url: url)) diff --git a/Packages/Status/Sources/Status/Row/StatusRowContextMenu.swift b/Packages/Status/Sources/Status/Row/StatusRowContextMenu.swift index 9e7ff3e2..63b6fce7 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowContextMenu.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowContextMenu.swift @@ -83,7 +83,8 @@ struct StatusRowContextMenu: View { } } label: { if let statusLang = viewModel.status.language, - let languageName = Locale.current.localizedString(forLanguageCode: statusLang) { + let languageName = Locale.current.localizedString(forLanguageCode: statusLang) + { Label("status.action.translate-from-\(languageName)", systemImage: "captions.bubble") } else { Label("status.action.translate", systemImage: "captions.bubble") @@ -113,8 +114,7 @@ struct StatusRowContextMenu: View { } Button(role: .destructive, action: { viewModel.showDeleteAlert = true }, - label: { Label("status.action.delete", systemImage: "trash") } - ) + label: { Label("status.action.delete", systemImage: "trash") }) } } else if !viewModel.isRemote { Section(viewModel.status.account.acct) { diff --git a/Packages/Status/Sources/Status/Row/StatusRowDetailView.swift b/Packages/Status/Sources/Status/Row/StatusRowDetailView.swift index 3a9df4fc..06d31bc6 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowDetailView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowDetailView.swift @@ -1,14 +1,14 @@ -import SwiftUI -import Env import DesignSystem +import Env import Models +import SwiftUI struct StatusRowDetailView: View { @Environment(\.openURL) private var openURL @EnvironmentObject private var routerPath: RouterPath - + @ObservedObject var viewModel: StatusRowViewModel - + var body: some View { Group { Divider() @@ -84,7 +84,7 @@ struct StatusRowDetailView: View { await viewModel.fetchActionsAccounts() } } - + private func makeAccountsScrollView(accounts: [Account]) -> some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 0) { @@ -96,5 +96,4 @@ struct StatusRowDetailView: View { .padding(.leading, .layoutPadding) } } - } diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 2ed7e901..34042cd2 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -118,19 +118,21 @@ public struct StatusRowView: View { title: Text("status.action.delete.confirm.title"), message: Text("status.action.delete.confirm.message"), primaryButton: .destructive( - Text("status.action.delete")) { - Task { - await viewModel.delete() - } - }, - secondaryButton: .cancel()) + Text("status.action.delete")) + { + Task { + await viewModel.delete() + } + }, + secondaryButton: .cancel() + ) }) .alignmentGuide(.listRowSeparatorLeading) { _ in -100 } } } - + @ViewBuilder private var accesibilityActions: some View { // Add the individual mentions as accessibility actions @@ -139,17 +141,17 @@ public struct StatusRowView: View { routerPath.navigate(to: .accountDetail(id: mention.id)) } } - + Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") { withAnimation { viewModel.displaySpoiler.toggle() } } - + Button("@\(viewModel.status.account.username)") { routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id)) } - + contextMenu } @@ -329,7 +331,7 @@ public struct StatusRowView: View { .foregroundColor(.gray) } } - + private var contextMenuButton: some View { Menu { contextMenu @@ -470,7 +472,7 @@ public struct StatusRowView: View { makeSwipeButton(action: preferences.swipeActionsStatusLeadingRight) } } - + @ViewBuilder private func makeSwipeButton(action: StatusAction) -> some View { switch action { @@ -500,14 +502,14 @@ public struct StatusRowView: View { await viewModel.unbookmark() } else { await - viewModel.bookmark() + viewModel.bookmark() } } case .none: EmptyView() } } - + @ViewBuilder private func makeSwipeButtonForRouterPath(action: StatusAction, destination: SheetDestinations) -> some View { Button { @@ -520,9 +522,9 @@ public struct StatusRowView: View { } .tint(action.color(themeTintColor: theme.tintColor)) } - + @ViewBuilder - private func makeSwipeButtonForTask(action: StatusAction, task: @escaping () async -> Void ) -> some View { + private func makeSwipeButtonForTask(action: StatusAction, task: @escaping () async -> Void) -> some View { Button { Task { HapticManager.shared.fireHaptic(of: .notification(.success)) diff --git a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift index 276e62c9..944ee337 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift @@ -34,7 +34,7 @@ public class StatusRowViewModel: ObservableObject { @Published var rebloggers: [Account] = [] private let theme = Theme.shared - + var seen = false var filter: Filtered? { @@ -42,7 +42,6 @@ public class StatusRowViewModel: ObservableObject { } var highlightRowColor: Color { - if status.visibility == .direct { return theme.tintColor.opacity(0.15) } else if status.userMentioned != nil { @@ -265,13 +264,13 @@ public class StatusRowViewModel: ObservableObject { _ = try await client.delete(endpoint: Statuses.status(id: status.id)) } catch {} } - + func fetchActionsAccounts() async { guard let client else { return } do { favoriters = try await client.get(endpoint: Statuses.favoritedBy(id: status.id, maxId: nil)) rebloggers = try await client.get(endpoint: Statuses.rebloggedBy(id: status.id, maxId: nil)) - } catch { } + } catch {} } private func updateFromStatus(status: Status) { diff --git a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift index 60bfd5db..ef0d1ccf 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift @@ -5,7 +5,7 @@ import SwiftUI public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable { case local, federated, trending - + public func localizedTitle() -> LocalizedStringKey { switch self { case .federated: @@ -16,7 +16,7 @@ public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable { return "timeline.trending" } } - + public func iconName() -> String { switch self { case .federated: @@ -196,7 +196,7 @@ extension TimelineFilter: Codable { } public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .home: try container.encode(CodingKeys.home.rawValue, forKey: .home) @@ -206,13 +206,13 @@ extension TimelineFilter: Codable { try container.encode(CodingKeys.federated.rawValue, forKey: .federated) case .trending: try container.encode(CodingKeys.trending.rawValue, forKey: .trending) - case .hashtag(let tag, let accountId): + case let .hashtag(tag, accountId): var nestedContainer = container.nestedUnkeyedContainer(forKey: .hashtag) try nestedContainer.encode(tag) try nestedContainer.encode(accountId) - case .list(let list): + case let .list(list): try container.encode(list, forKey: .list) - case .remoteLocal(let server, let filter): + case let .remoteLocal(server, filter): var nestedContainer = container.nestedUnkeyedContainer(forKey: .hashtag) try nestedContainer.encode(server) try nestedContainer.encode(filter) @@ -260,17 +260,17 @@ extension RemoteTimelineFilter: Codable { case .trending: self = .trending default: - throw DecodingError.dataCorrupted( - DecodingError.Context( - codingPath: container.codingPath, - debugDescription: "Unabled to decode enum." - ) + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode enum." ) + ) } } public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .local: try container.encode(CodingKeys.local.rawValue, forKey: .local) diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift index c6acf91c..988944b5 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift @@ -65,7 +65,8 @@ public struct TimelineView: View { if let collectionView, let index, let rows = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: 0), - rows > index { + rows > index + { collectionView.scrollToItem(at: .init(row: index, section: 0), at: .top, animated: viewModel.scrollToIndexAnimated) diff --git a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift index 9f31a6ef..1cfec911 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift @@ -34,7 +34,7 @@ class TimelineViewModel: ObservableObject { } } } - + private var timelineTask: Task? @Published var tag: Tag? @@ -170,7 +170,7 @@ extension TimelineViewModel: StatusesFetcher { // Hydrate statuses in the Timeline when statuses are empty. private func fetchFirstPage(client: Client) async throws { pendingStatusesObserver.pendingStatuses = [] - + if statuses.isEmpty { statusesState = .loading } @@ -208,7 +208,7 @@ extension TimelineViewModel: StatusesFetcher { ReblogCache.shared.removeDuplicateReblogs(&statuses) await cacheHome() - + withAnimation { statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage) } @@ -238,7 +238,7 @@ extension TimelineViewModel: StatusesFetcher { canStreamEvents = true return } - + // Return if task has been cancelled. guard !Task.isCancelled else { canStreamEvents = true @@ -283,7 +283,7 @@ extension TimelineViewModel: StatusesFetcher { canStreamEvents = true } } - + // We trigger a new fetch so we can get the next new statuses if any. // If none, it'll stop there. if !Task.isCancelled, let latest = statuses.first, let client {