mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-04 00:17:30 +01:00
Swiftformat
This commit is contained in:
parent
383eb84e98
commit
4e77669181
@ -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) {}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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]
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user