Swiftformat

This commit is contained in:
Thomas Ricouard 2023-02-01 12:49:59 +01:00
parent 383eb84e98
commit 4e77669181
9 changed files with 52 additions and 51 deletions

View File

@ -246,8 +246,8 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
} }
} }
} }
func statusDidAppear(status: Models.Status) { } func statusDidAppear(status _: Models.Status) {}
func statusDidDisappear(status: Status) { } func statusDidDisappear(status _: Status) {}
} }

View File

@ -36,7 +36,7 @@ public struct HTMLString: Decodable, Equatable, Hashable {
if let regex = try? NSRegularExpression(pattern: "(<span class=\"ellipsis\">(.*?)</span>)", options: .caseInsensitive) { if let regex = try? NSRegularExpression(pattern: "(<span class=\"ellipsis\">(.*?)</span>)", options: .caseInsensitive) {
htmlValue = regex.stringByReplacingMatches(in: htmlValue, options: [], range: NSRange(location: 0, length: htmlValue.count), withTemplate: "$2…") htmlValue = regex.stringByReplacingMatches(in: htmlValue, options: [], range: NSRange(location: 0, length: htmlValue.count), withTemplate: "$2…")
} }
do { do {
asMarkdown = try HTMLParser().parse(html: htmlValue) asMarkdown = try HTMLParser().parse(html: htmlValue)
.toMarkdown() .toMarkdown()

View File

@ -17,11 +17,11 @@ public class Client: ObservableObject, Equatable, Identifiable, Hashable {
case missingApp case missingApp
case invalidRedirectURL case invalidRedirectURL
} }
public var id: String { public var id: String {
"\(isAuth)\(server)\(oauthToken?.accessToken ?? "")" "\(isAuth)\(server)\(oauthToken?.accessToken ?? "")"
} }
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(id) hasher.combine(id)
} }

View File

@ -201,7 +201,7 @@ public class StatusEditorViewModel: ObservableObject {
mentionString += "@\(mention.acct)" mentionString += "@\(mention.acct)"
} }
if !mentionString.isEmpty { if !mentionString.isEmpty {
mentionString += " " mentionString += " "
} }
replyToStatus = status replyToStatus = status
visibility = status.visibility visibility = status.visibility

View File

@ -5,7 +5,7 @@ import SwiftUI
public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher { public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ObservedObject private var fetcher: Fetcher @ObservedObject private var fetcher: Fetcher
private let isRemote: Bool private let isRemote: Bool
private let isEmbdedInList: Bool private let isEmbdedInList: Bool

View File

@ -1,29 +1,29 @@
import Foundation import Foundation
import SwiftUI
import Models import Models
import SwiftUI
@MainActor @MainActor
class PendingStatusesObserver: ObservableObject { class PendingStatusesObserver: ObservableObject {
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light) let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
@Published var pendingStatusesCount: Int = 0 @Published var pendingStatusesCount: Int = 0
var disableUpdate: Bool = false var disableUpdate: Bool = false
var pendingStatuses: [String] = [] { var pendingStatuses: [String] = [] {
didSet { didSet {
pendingStatusesCount = pendingStatuses.count pendingStatusesCount = pendingStatuses.count
} }
} }
func removeStatus(status: Status) { func removeStatus(status: Status) {
if !disableUpdate, let index = pendingStatuses.firstIndex(of: status.id) { if !disableUpdate, let index = pendingStatuses.firstIndex(of: status.id) {
pendingStatuses.removeSubrange(index...(pendingStatuses.count - 1)) pendingStatuses.removeSubrange(index ... (pendingStatuses.count - 1))
feedbackGenerator.impactOccurred() feedbackGenerator.impactOccurred()
} }
} }
init() { } init() {}
} }
struct PendingStatusesObserverView: View { struct PendingStatusesObserverView: View {
@ -32,7 +32,7 @@ struct PendingStatusesObserverView: View {
if observer.pendingStatusesCount > 0 { if observer.pendingStatusesCount > 0 {
HStack(spacing: 6) { HStack(spacing: 6) {
Spacer() Spacer()
Button { } label: { Button {} label: {
Text("\(observer.pendingStatusesCount)") Text("\(observer.pendingStatusesCount)")
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)

View File

@ -1,18 +1,18 @@
import SwiftUI
import Models import Models
import Network import Network
import SwiftUI
actor TimelineCache { actor TimelineCache {
static let shared: TimelineCache = .init() static let shared: TimelineCache = .init()
private var memoryCache: [Client: [Status]] = [:] private var memoryCache: [Client: [Status]] = [:]
private init() {} private init() {}
func set(statuses: [Status], client: Client) { func set(statuses: [Status], client: Client) {
memoryCache[client] = statuses.prefix(upTo: min(100, (statuses.count - 1))).map{ $0 } memoryCache[client] = statuses.prefix(upTo: min(100, statuses.count - 1)).map { $0 }
} }
func getStatuses(for client: Client) -> [Status]? { func getStatuses(for client: Client) -> [Status]? {
memoryCache[client] memoryCache[client]
} }

View File

@ -19,9 +19,9 @@ public struct TimelineView: View {
@EnvironmentObject private var routerPath: RouterPath @EnvironmentObject private var routerPath: RouterPath
@StateObject private var viewModel = TimelineViewModel() @StateObject private var viewModel = TimelineViewModel()
@State private var wasBackgrounded: Bool = false @State private var wasBackgrounded: Bool = false
@Binding var timeline: TimelineFilter @Binding var timeline: TimelineFilter
@Binding var scrollToTopSignal: Int @Binding var scrollToTopSignal: Int
@ -111,7 +111,7 @@ public struct TimelineView: View {
} }
case .background: case .background:
wasBackgrounded = true wasBackgrounded = true
default: default:
break break
} }
@ -154,9 +154,9 @@ public struct TimelineView: View {
trailing: .layoutPadding)) trailing: .layoutPadding))
} }
} }
private var scrollToTopView: some View { private var scrollToTopView: some View {
HStack{ EmptyView() } HStack { EmptyView() }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowInsets(.init()) .listRowInsets(.init())

View File

@ -18,14 +18,14 @@ class TimelineViewModel: ObservableObject {
private var statuses: [Status] = [] private var statuses: [Status] = []
private var visibileStatusesIds = Set<String>() private var visibileStatusesIds = Set<String>()
var scrollToTopVisible: Bool = false var scrollToTopVisible: Bool = false
private var canStreamEvents: Bool = true private var canStreamEvents: Bool = true
let pendingStatusesObserver: PendingStatusesObserver = .init() let pendingStatusesObserver: PendingStatusesObserver = .init()
let cache: TimelineCache = .shared let cache: TimelineCache = .shared
@Published var scrollToStatus: String? @Published var scrollToStatus: String?
@Published var statusesState: StatusesState = .loading @Published var statusesState: StatusesState = .loading
@Published var timeline: TimelineFilter = .federated { @Published var timeline: TimelineFilter = .federated {
didSet { didSet {
@ -57,7 +57,6 @@ class TimelineViewModel: ObservableObject {
client?.server ?? "Error" client?.server ?? "Error"
} }
func fetchTag(id: String) async { func fetchTag(id: String) async {
guard let client else { return } guard let client else { return }
do { do {
@ -65,7 +64,7 @@ class TimelineViewModel: ObservableObject {
} catch {} } catch {}
} }
func handleEvent(event: any StreamEvent, currentAccount: CurrentAccount) { func handleEvent(event: any StreamEvent, currentAccount _: CurrentAccount) {
if let event = event as? StreamEventUpdate, if let event = event as? StreamEventUpdate,
canStreamEvents, canStreamEvents,
pendingStatusesEnabled, pendingStatusesEnabled,
@ -91,13 +90,14 @@ class TimelineViewModel: ObservableObject {
} }
// MARK: - Cache // MARK: - Cache
extension TimelineViewModel { extension TimelineViewModel {
private func cache(statuses: [Status]) async { private func cache(statuses: [Status]) async {
if let client { if let client {
await cache.set(statuses: statuses, client: client) await cache.set(statuses: statuses, client: client)
} }
} }
private func getCachedStatuses() async -> [Status]? { private func getCachedStatuses() async -> [Status]? {
if let client { if let client {
return await cache.getStatuses(for: client) return await cache.getStatuses(for: client)
@ -107,6 +107,7 @@ extension TimelineViewModel {
} }
// MARK: - StatusesFetcher // MARK: - StatusesFetcher
extension TimelineViewModel: StatusesFetcher { extension TimelineViewModel: StatusesFetcher {
func fetchStatuses() async { func fetchStatuses() async {
guard let client else { return } guard let client else { return }
@ -122,12 +123,12 @@ extension TimelineViewModel: StatusesFetcher {
print("timeline parse error: \(error)") print("timeline parse error: \(error)")
} }
} }
// Hydrate statuses in the Timeline when statuses are empty. // Hydrate statuses in the Timeline when statuses are empty.
private func fetchFirstPage(client: Client) async throws { private func fetchFirstPage(client: Client) async throws {
pendingStatusesObserver.pendingStatuses = [] pendingStatusesObserver.pendingStatuses = []
statusesState = .loading statusesState = .loading
// If we get statuses from the cache for the home timeline, we displays those. // If we get statuses from the cache for the home timeline, we displays those.
// Else we fetch top most page from the API. // Else we fetch top most page from the API.
if let cachedStatuses = await getCachedStatuses(), timeline == .home { if let cachedStatuses = await getCachedStatuses(), timeline == .home {
@ -150,34 +151,34 @@ extension TimelineViewModel: StatusesFetcher {
} }
} }
} }
// Fetch pages from the top most status of the tomeline. // Fetch pages from the top most status of the tomeline.
private func fetchNewPagesFrom(latestStatus: Status, client: Client) async throws { private func fetchNewPagesFrom(latestStatus: Status, client _: Client) async throws {
canStreamEvents = false canStreamEvents = false
var newStatuses: [Status] = await fetchNewPages(minId: latestStatus.id, maxPages: 10) var newStatuses: [Status] = await fetchNewPages(minId: latestStatus.id, maxPages: 10)
// Dedup statuses, a status with the same id could have been streamed in. // Dedup statuses, a status with the same id could have been streamed in.
newStatuses = newStatuses.filter { status in newStatuses = newStatuses.filter { status in
!statuses.contains(where: { $0.id == status.id }) !statuses.contains(where: { $0.id == status.id })
} }
// If no new statuses, resume streaming and exit. // If no new statuses, resume streaming and exit.
guard !newStatuses.isEmpty else { guard !newStatuses.isEmpty else {
canStreamEvents = true canStreamEvents = true
return return
} }
// Keep track of the top most status, so we can scroll back to it after view update. // Keep track of the top most status, so we can scroll back to it after view update.
let topStatusId = statuses.first?.id let topStatusId = statuses.first?.id
// Insert new statuses in internal datasource. // Insert new statuses in internal datasource.
statuses.insert(contentsOf: newStatuses, at: 0) statuses.insert(contentsOf: newStatuses, at: 0)
// Cache statuses for home timeline. // Cache statuses for home timeline.
if timeline == .home { if timeline == .home {
await cache(statuses: statuses) await cache(statuses: statuses)
} }
// If pending statuses are not enabled, we simply load status on the top regardless of the current position. // If pending statuses are not enabled, we simply load status on the top regardless of the current position.
if !pendingStatusesEnabled { if !pendingStatusesEnabled {
pendingStatusesObserver.pendingStatuses = [] pendingStatusesObserver.pendingStatuses = []
@ -187,9 +188,9 @@ extension TimelineViewModel: StatusesFetcher {
} }
} else { } else {
// Append new statuses in the timeline indicator. // Append new statuses in the timeline indicator.
pendingStatusesObserver.pendingStatuses.insert(contentsOf: newStatuses.map{ $0.id }, at: 0) pendingStatusesObserver.pendingStatuses.insert(contentsOf: newStatuses.map { $0.id }, at: 0)
pendingStatusesObserver.feedbackGenerator.impactOccurred() pendingStatusesObserver.feedbackGenerator.impactOccurred()
// High chance the user is scrolled to the top. // High chance the user is scrolled to the top.
// We need to update the statuses state, and then scroll to the previous top most status. // We need to update the statuses state, and then scroll to the previous top most status.
if let topStatusId, visibileStatusesIds.contains(topStatusId), scrollToTopVisible { if let topStatusId, visibileStatusesIds.contains(topStatusId), scrollToTopVisible {
@ -209,7 +210,7 @@ extension TimelineViewModel: StatusesFetcher {
} }
} }
} }
private func fetchNewPages(minId: String, maxPages: Int) async -> [Status] { private func fetchNewPages(minId: String, maxPages: Int) async -> [Status] {
guard let client else { return [] } guard let client else { return [] }
var pagesLoaded = 0 var pagesLoaded = 0
@ -232,7 +233,7 @@ extension TimelineViewModel: StatusesFetcher {
} }
return allStatuses return allStatuses
} }
func fetchNextPage() async { func fetchNextPage() async {
guard let client else { return } guard let client else { return }
do { do {
@ -248,12 +249,12 @@ extension TimelineViewModel: StatusesFetcher {
statusesState = .error(error: error) statusesState = .error(error: error)
} }
} }
func statusDidAppear(status: Status) { func statusDidAppear(status: Status) {
pendingStatusesObserver.removeStatus(status: status) pendingStatusesObserver.removeStatus(status: status)
visibileStatusesIds.insert(status.id) visibileStatusesIds.insert(status.id)
} }
func statusDidDisappear(status: Status) { func statusDidDisappear(status: Status) {
visibileStatusesIds.remove(status.id) visibileStatusesIds.remove(status.id)
} }