diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift
index 10c5abe6..812797b5 100644
--- a/Packages/Account/Sources/Account/AccountDetailView.swift
+++ b/Packages/Account/Sources/Account/AccountDetailView.swift
@@ -21,8 +21,7 @@ public struct AccountDetailView: View {
ScrollView {
LazyVStack {
headerView
- statusesView
- .padding(.horizontal, 16)
+ StatusesListView(fetcher: viewModel)
}
}
.edgesIgnoringSafeArea(.top)
@@ -46,47 +45,6 @@ public struct AccountDetailView: View {
}
}
-
- @ViewBuilder
- private var statusesView: some View {
- switch viewModel.statusesState {
- case .loading:
- ForEach(Status.placeholders()) { status in
- StatusRowView(status: status)
- .redacted(reason: .placeholder)
- .shimmering()
- Divider()
- }
- case let .error(error):
- Text(error.localizedDescription)
- case let .display(statuses, nextPageState):
- ForEach(statuses) { status in
- StatusRowView(status: status)
- Divider()
- .padding(.bottom, DS.Constants.layoutPadding)
- }
-
- switch nextPageState {
- case .hasNextPage:
- loadingRow
- .onAppear {
- Task {
- await viewModel.loadNextPage()
- }
- }
- case .loadingNextPage:
- loadingRow
- }
- }
- }
-
- private var loadingRow: some View {
- HStack {
- Spacer()
- ProgressView()
- Spacer()
- }
- }
}
struct AccountDetailView_Previews: PreviewProvider {
diff --git a/Packages/Account/Sources/Account/AccountDetailViewModel.swift b/Packages/Account/Sources/Account/AccountDetailViewModel.swift
index 9ae11e2a..03d9c008 100644
--- a/Packages/Account/Sources/Account/AccountDetailViewModel.swift
+++ b/Packages/Account/Sources/Account/AccountDetailViewModel.swift
@@ -1,24 +1,17 @@
import SwiftUI
import Network
import Models
+import Status
@MainActor
-class AccountDetailViewModel: ObservableObject {
+class AccountDetailViewModel: ObservableObject, StatusesFetcher {
let accountId: String
var client: Client = .init(server: "")
enum State {
case loading, data(account: Account), error(error: Error)
}
-
- enum StatusesState {
- enum PagingState {
- case hasNextPage, loadingNextPage
- }
- case loading
- case display(statuses: [Status], nextPageState: StatusesState.PagingState)
- case error(error: Error)
- }
+
@Published var state: State = .loading
@Published var statusesState: StatusesState = .loading
@@ -52,7 +45,7 @@ class AccountDetailViewModel: ObservableObject {
}
}
- func loadNextPage() async {
+ func fetchNextPage() async {
do {
guard let lastId = statuses.last?.id else { return }
statusesState = .display(statuses: statuses, nextPageState: .loadingNextPage)
diff --git a/Packages/Status/.swiftpm/xcode/xcshareddata/xcschemes/Status.xcscheme b/Packages/Status/.swiftpm/xcode/xcshareddata/xcschemes/Status.xcscheme
new file mode 100644
index 00000000..d245aea2
--- /dev/null
+++ b/Packages/Status/.swiftpm/xcode/xcshareddata/xcschemes/Status.xcscheme
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Packages/Status/Package.swift b/Packages/Status/Package.swift
index 0f1d4830..b1a8930f 100644
--- a/Packages/Status/Package.swift
+++ b/Packages/Status/Package.swift
@@ -17,6 +17,7 @@ let package = Package(
.package(name: "Models", path: "../Models"),
.package(name: "Routeur", path: "../Routeur"),
.package(name: "DesignSystem", path: "../DesignSystem"),
+ .package(url: "https://github.com/markiv/SwiftUI-Shimmer", exact: "1.1.0")
],
targets: [
.target(
@@ -25,6 +26,7 @@ let package = Package(
.product(name: "Models", package: "Models"),
.product(name: "Routeur", package: "Routeur"),
.product(name: "DesignSystem", package: "DesignSystem"),
+ .product(name: "Shimmer", package: "SwiftUI-Shimmer")
]),
]
)
diff --git a/Packages/Status/Sources/Status/StatusesListView.swift b/Packages/Status/Sources/Status/StatusesListView.swift
new file mode 100644
index 00000000..7da6299e
--- /dev/null
+++ b/Packages/Status/Sources/Status/StatusesListView.swift
@@ -0,0 +1,72 @@
+import SwiftUI
+import Models
+import Shimmer
+import DesignSystem
+
+public enum StatusesState {
+ public enum PagingState {
+ case hasNextPage, loadingNextPage
+ }
+ case loading
+ case display(statuses: [Status], nextPageState: StatusesState.PagingState)
+ case error(error: Error)
+}
+
+@MainActor
+public protocol StatusesFetcher: ObservableObject {
+ var statusesState: StatusesState { get }
+ func fetchStatuses() async
+ func fetchNextPage() async
+}
+
+public struct StatusesListView: View where Fetcher: StatusesFetcher {
+ @ObservedObject private var fetcher: Fetcher
+
+ public init(fetcher: Fetcher) {
+ self.fetcher = fetcher
+ }
+
+ public var body: some View {
+ Group {
+ switch fetcher.statusesState {
+ case .loading:
+ ForEach(Status.placeholders()) { status in
+ StatusRowView(status: status)
+ .redacted(reason: .placeholder)
+ .shimmering()
+ Divider()
+ .padding(.bottom, DS.Constants.layoutPadding)
+ }
+ case let .error(error):
+ Text(error.localizedDescription)
+ case let .display(statuses, nextPageState):
+ ForEach(statuses) { status in
+ StatusRowView(status: status)
+ Divider()
+ .padding(.bottom, DS.Constants.layoutPadding)
+ }
+
+ switch nextPageState {
+ case .hasNextPage:
+ loadingRow
+ .onAppear {
+ Task {
+ await fetcher.fetchNextPage()
+ }
+ }
+ case .loadingNextPage:
+ loadingRow
+ }
+ }
+ }
+ .padding(.horizontal, 16)
+ }
+
+ private var loadingRow: some View {
+ HStack {
+ Spacer()
+ ProgressView()
+ Spacer()
+ }
+ }
+}
diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift
index bc8fe37d..1ea0086f 100644
--- a/Packages/Timeline/Sources/Timeline/TimelineView.swift
+++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift
@@ -12,34 +12,11 @@ public struct TimelineView: View {
public init() {}
public var body: some View {
- List {
- switch viewModel.state {
- case .loading:
- ForEach(Status.placeholders()) { placeholder in
- StatusRowView(status: placeholder)
- .redacted(reason: .placeholder)
- .shimmering()
- }
- case let .error(error):
- Text(error.localizedDescription)
- 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
- }
+ ScrollView {
+ LazyVStack {
+ StatusesListView(fetcher: viewModel)
}
}
- .listStyle(.plain)
.navigationTitle(viewModel.timeline.rawValue)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -50,22 +27,15 @@ public struct TimelineView: View {
.task {
viewModel.client = client
if !didAppear {
- await viewModel.refreshTimeline()
+ await viewModel.fetchStatuses()
didAppear = true
}
}
.refreshable {
- await viewModel.refreshTimeline()
+ await viewModel.fetchStatuses()
}
}
- private var loadingRow: some View {
- HStack {
- Spacer()
- ProgressView()
- Spacer()
- }
- }
private var timelineFilterButton: some View {
Menu {
diff --git a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift
index a02103e1..85439022 100644
--- a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift
+++ b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift
@@ -1,18 +1,10 @@
import SwiftUI
import Network
import Models
+import Status
@MainActor
-class TimelineViewModel: ObservableObject {
- enum State {
- enum PagingState {
- case hasNextPage, loadingNextPage
- }
- case loading
- case display(statuses: [Status], nextPageState: State.PagingState)
- case error(error: Error)
- }
-
+class TimelineViewModel: ObservableObject, StatusesFetcher {
enum TimelineFilter: String, CaseIterable {
case pub = "Public"
case home = "Home"
@@ -33,12 +25,12 @@ class TimelineViewModel: ObservableObject {
private var statuses: [Status] = []
- @Published var state: State = .loading
+ @Published var statusesState: StatusesState = .loading
@Published var timeline: TimelineFilter = .pub {
didSet {
if oldValue != timeline {
Task {
- await refreshTimeline()
+ await fetchStatuses()
}
}
}
@@ -48,25 +40,25 @@ class TimelineViewModel: ObservableObject {
client.server
}
- func refreshTimeline() async {
+ func fetchStatuses() async {
do {
- state = .loading
+ statusesState = .loading
statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil))
- state = .display(statuses: statuses, nextPageState: .hasNextPage)
+ statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
} catch {
- state = .error(error: error)
+ statusesState = .error(error: error)
}
}
- func loadNextPage() async {
+ func fetchNextPage() async {
do {
guard let lastId = statuses.last?.id else { return }
- state = .display(statuses: statuses, nextPageState: .loadingNextPage)
+ statusesState = .display(statuses: statuses, nextPageState: .loadingNextPage)
let newStatuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: lastId))
statuses.append(contentsOf: newStatuses)
- state = .display(statuses: statuses, nextPageState: .hasNextPage)
+ statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
} catch {
- state = .error(error: error)
+ statusesState = .error(error: error)
}
}
}