mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-23 05:19:42 +01:00
Paginated tiemeline and refactoring
This commit is contained in:
parent
0608996bb8
commit
ec8de5fb83
@ -13,8 +13,7 @@ struct IceCubesAppApp: App {
|
||||
TabView {
|
||||
ForEach(tabs, id: \.self) { tab in
|
||||
NavigationStack {
|
||||
TimelineView(kind: .pub)
|
||||
.environmentObject(Client(server: tab))
|
||||
TimelineView(client: .init(server: tab))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
|
@ -23,6 +23,7 @@ public class Client: ObservableObject {
|
||||
components.scheme = "https"
|
||||
components.host = server
|
||||
components.path += "/api/\(version.rawValue)/\(endpoint.path())"
|
||||
components.queryItems = endpoint.queryItems()
|
||||
return components.url!
|
||||
}
|
||||
|
||||
|
@ -2,4 +2,5 @@ import Foundation
|
||||
|
||||
public protocol Endpoint {
|
||||
func path() -> String
|
||||
func queryItems() -> [URLQueryItem]?
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public enum Timeline: Endpoint {
|
||||
case pub
|
||||
case pub(sinceId: String?)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
@ -9,4 +9,11 @@ public enum Timeline: Endpoint {
|
||||
return "timelines/public"
|
||||
}
|
||||
}
|
||||
|
||||
public func queryItems() -> [URLQueryItem]? {
|
||||
switch self {
|
||||
case .pub(let sinceId):
|
||||
return [.init(name: "max_id", value: sinceId)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ public struct Status: Codable, Identifiable {
|
||||
public let id: String
|
||||
public let content: String
|
||||
public let account: Account
|
||||
public let createdAt: String
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import SwiftSoup
|
||||
import HTML2Markdown
|
||||
import Foundation
|
||||
|
||||
extension Status {
|
||||
public var contentAsMarkdown: String {
|
||||
@ -10,4 +10,18 @@ extension Status {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
public var createdAtDate: Date {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.calendar = .init(identifier: .iso8601)
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
|
||||
dateFormatter.timeZone = .init(abbreviation: "UTC")
|
||||
return dateFormatter.date(from: createdAt)!
|
||||
}
|
||||
|
||||
public var createdAtFormatted: String {
|
||||
let dateFormatter = RelativeDateTimeFormatter()
|
||||
dateFormatter.unitsStyle = .abbreviated
|
||||
return dateFormatter.localizedString(for: createdAtDate, relativeTo: Date())
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ struct StatusRowView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
HStack(alignment: .top) {
|
||||
AsyncImage(
|
||||
url: status.account.avatar,
|
||||
content: { image in
|
||||
@ -27,6 +27,10 @@ struct StatusRowView: View {
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Spacer()
|
||||
Text(status.createdAtFormatted)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Text(try! AttributedString(markdown: status.contentAsMarkdown))
|
||||
}
|
||||
|
@ -2,40 +2,52 @@ import SwiftUI
|
||||
import Network
|
||||
|
||||
public struct TimelineView: View {
|
||||
public enum Kind {
|
||||
case pub, hastah, home, list
|
||||
}
|
||||
@StateObject private var viewModel: TimelineViewModel
|
||||
|
||||
@EnvironmentObject private var client: Client
|
||||
|
||||
@State private var statuses: [Status] = []
|
||||
|
||||
private let kind: Kind
|
||||
|
||||
public init(kind: Kind) {
|
||||
self.kind = kind
|
||||
public init(client: Client) {
|
||||
_viewModel = StateObject(wrappedValue: TimelineViewModel(client: client))
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
List(statuses) { status in
|
||||
StatusRowView(status: status)
|
||||
List {
|
||||
switch viewModel.state {
|
||||
case .loading:
|
||||
loadingRow
|
||||
case .error:
|
||||
Text("An error occurred, please try to refresh")
|
||||
case let .display(statuses, nextPageState):
|
||||
ForEach(statuses) { status in
|
||||
StatusRowView(status: status)
|
||||
}
|
||||
switch nextPageState {
|
||||
case .hasNextPage:
|
||||
loadingRow
|
||||
.onAppear {
|
||||
Task {
|
||||
await viewModel.loadNextPage()
|
||||
}
|
||||
}
|
||||
case .loadingNextPage:
|
||||
loadingRow
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Public Timeline: \(client.server)")
|
||||
.navigationTitle("Public Timeline: \(viewModel.serverName)")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.task {
|
||||
await refreshTimeline()
|
||||
await viewModel.refreshTimeline()
|
||||
}
|
||||
.refreshable {
|
||||
await refreshTimeline()
|
||||
await viewModel.refreshTimeline()
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshTimeline() async {
|
||||
do {
|
||||
self.statuses = try await client.fetchArray(endpoint: Timeline.pub)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
private var loadingRow: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
48
Packages/Timeline/Sources/Timeline/TimelineViewModel.swift
Normal file
48
Packages/Timeline/Sources/Timeline/TimelineViewModel.swift
Normal file
@ -0,0 +1,48 @@
|
||||
import SwiftUI
|
||||
import Network
|
||||
|
||||
@MainActor
|
||||
class TimelineViewModel: ObservableObject {
|
||||
enum State {
|
||||
enum PadingState {
|
||||
case hasNextPage, loadingNextPage
|
||||
}
|
||||
case loading
|
||||
case display(statuses: [Status], nextPageState: State.PadingState)
|
||||
case error
|
||||
}
|
||||
|
||||
private let client: Client
|
||||
private var statuses: [Status] = []
|
||||
|
||||
@Published var state: State = .loading
|
||||
|
||||
var serverName: String {
|
||||
client.server
|
||||
}
|
||||
|
||||
init(client: Client) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
func refreshTimeline() async {
|
||||
do {
|
||||
statuses = try await client.fetchArray(endpoint: Timeline.pub(sinceId: nil))
|
||||
state = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func loadNextPage() async {
|
||||
do {
|
||||
guard let lastId = statuses.last?.id else { return }
|
||||
state = .display(statuses: statuses, nextPageState: .loadingNextPage)
|
||||
let newStatuses: [Status] = try await client.fetch(endpoint: Timeline.pub(sinceId: lastId))
|
||||
statuses.append(contentsOf: newStatuses)
|
||||
state = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user