mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-01-18 12:05:34 +00:00
Editor: Simple edit
This commit is contained in:
parent
fded30bb76
commit
bda77571b6
17 changed files with 165 additions and 39 deletions
|
@ -27,6 +27,15 @@
|
||||||
"version" : "11.5.0"
|
"version" : "11.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftsoup",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/scinfu/SwiftSoup.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "6778575285177365cbad3e5b8a72f2a20583cfec",
|
||||||
|
"version" : "2.4.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swiftui-shimmer",
|
"identity" : "swiftui-shimmer",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|
|
@ -32,8 +32,12 @@ extension View {
|
||||||
func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View {
|
func withSheetDestinations(sheetDestinations: Binding<SheetDestinations?>) -> some View {
|
||||||
self.sheet(item: sheetDestinations) { destination in
|
self.sheet(item: sheetDestinations) { destination in
|
||||||
switch destination {
|
switch destination {
|
||||||
case let .statusEditor(replyToStatus):
|
case let .replyToStatusEditor(status):
|
||||||
StatusEditorView(inReplyTo: replyToStatus)
|
StatusEditorView(mode: .replyTo(status: status))
|
||||||
|
case .newStatusEditor:
|
||||||
|
StatusEditorView(mode: .new)
|
||||||
|
case let .editStatusEditor(status):
|
||||||
|
StatusEditorView(mode: .edit(status: status))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ struct TimelineTab: View {
|
||||||
if client.isAuth {
|
if client.isAuth {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
Button {
|
Button {
|
||||||
routeurPath.presentedSheet = .statusEditor(replyToStatus: nil)
|
routeurPath.presentedSheet = .newStatusEditor
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.and.pencil")
|
Image(systemName: "square.and.pencil")
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,13 @@ public enum RouteurDestinations: Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SheetDestinations: Identifiable {
|
public enum SheetDestinations: Identifiable {
|
||||||
case statusEditor(replyToStatus: Status?)
|
case newStatusEditor
|
||||||
|
case editStatusEditor(status: Status)
|
||||||
|
case replyToStatusEditor(status: Status)
|
||||||
|
|
||||||
public var id: String {
|
public var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .statusEditor:
|
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor:
|
||||||
return "statusEditor"
|
return "statusEditor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,14 @@ let package = Package(
|
||||||
targets: ["Models"]),
|
targets: ["Models"]),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://gitlab.com/mflint/HTML2Markdown", exact: "1.0.0")
|
.package(url: "https://gitlab.com/mflint/HTML2Markdown", exact: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.4.3"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "Models",
|
name: "Models",
|
||||||
dependencies: ["HTML2Markdown"]),
|
dependencies: ["HTML2Markdown",
|
||||||
|
"SwiftSoup"]),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "ModelsTests",
|
name: "ModelsTests",
|
||||||
dependencies: ["Models"]),
|
dependencies: ["Models"]),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import HTML2Markdown
|
import HTML2Markdown
|
||||||
|
import SwiftSoup
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public typealias HTMLString = String
|
public typealias HTMLString = String
|
||||||
|
@ -14,6 +15,15 @@ extension HTMLString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var asRawText: String {
|
||||||
|
do {
|
||||||
|
let document: Document = try SwiftSoup.parse(self)
|
||||||
|
return try document.text()
|
||||||
|
} catch {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var asSafeAttributedString: AttributedString {
|
public var asSafeAttributedString: AttributedString {
|
||||||
do {
|
do {
|
||||||
// Add space between hashtags and mentions that follow each other
|
// Add space between hashtags and mentions that follow each other
|
||||||
|
|
|
@ -8,10 +8,12 @@ public struct Application: Codable, Identifiable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol AnyStatus {
|
public protocol AnyStatus {
|
||||||
|
var viewId: String { get }
|
||||||
var id: String { get }
|
var id: String { get }
|
||||||
var content: HTMLString { get }
|
var content: HTMLString { get }
|
||||||
var account: Account { get }
|
var account: Account { get }
|
||||||
var createdAt: String { get }
|
var createdAt: ServerDate { get }
|
||||||
|
var editedAt: ServerDate? { get }
|
||||||
var mediaAttachments: [MediaAttachement] { get }
|
var mediaAttachments: [MediaAttachement] { get }
|
||||||
var mentions: [Mention] { get }
|
var mentions: [Mention] { get }
|
||||||
var repliesCount: Int { get }
|
var repliesCount: Int { get }
|
||||||
|
@ -27,11 +29,17 @@ public protocol AnyStatus {
|
||||||
var inReplyToAccountId: String? { get }
|
var inReplyToAccountId: String? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public struct Status: AnyStatus, Codable, Identifiable {
|
public struct Status: AnyStatus, Codable, Identifiable {
|
||||||
|
public var viewId: String {
|
||||||
|
id + createdAt + (editedAt ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
public let id: String
|
public let id: String
|
||||||
public let content: HTMLString
|
public let content: HTMLString
|
||||||
public let account: Account
|
public let account: Account
|
||||||
public let createdAt: ServerDate
|
public let createdAt: ServerDate
|
||||||
|
public let editedAt: ServerDate?
|
||||||
public let reblog: ReblogStatus?
|
public let reblog: ReblogStatus?
|
||||||
public let mediaAttachments: [MediaAttachement]
|
public let mediaAttachments: [MediaAttachement]
|
||||||
public let mentions: [Mention]
|
public let mentions: [Mention]
|
||||||
|
@ -52,6 +60,7 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
||||||
content: "Some post content\n Some more post content \n Some more",
|
content: "Some post content\n Some more post content \n Some more",
|
||||||
account: .placeholder(),
|
account: .placeholder(),
|
||||||
createdAt: "2022-12-16T10:20:54.000Z",
|
createdAt: "2022-12-16T10:20:54.000Z",
|
||||||
|
editedAt: nil,
|
||||||
reblog: nil,
|
reblog: nil,
|
||||||
mediaAttachments: [],
|
mediaAttachments: [],
|
||||||
mentions: [],
|
mentions: [],
|
||||||
|
@ -74,10 +83,15 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ReblogStatus: AnyStatus, Codable, Identifiable {
|
public struct ReblogStatus: AnyStatus, Codable, Identifiable {
|
||||||
|
public var viewId: String {
|
||||||
|
id + createdAt + (editedAt ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
public let id: String
|
public let id: String
|
||||||
public let content: String
|
public let content: String
|
||||||
public let account: Account
|
public let account: Account
|
||||||
public let createdAt: String
|
public let createdAt: String
|
||||||
|
public let editedAt: ServerDate?
|
||||||
public let mediaAttachments: [MediaAttachement]
|
public let mediaAttachments: [MediaAttachement]
|
||||||
public let mentions: [Mention]
|
public let mentions: [Mention]
|
||||||
public let repliesCount: Int
|
public let repliesCount: Int
|
||||||
|
|
|
@ -68,9 +68,7 @@ public class Client: ObservableObject, Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
|
public func get<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
|
||||||
let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint))
|
try await makeEntityRequest(endpoint: endpoint, method: "GET")
|
||||||
logResponseOnError(httpResponse: httpResponse, data: data)
|
|
||||||
return try decoder.decode(Entity.self, from: data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getWithLink<Entity: Decodable>(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) {
|
public func getWithLink<Entity: Decodable>(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) {
|
||||||
|
@ -85,11 +83,11 @@ public class Client: ObservableObject, Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func post<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
|
public func post<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
|
||||||
let url = makeURL(endpoint: endpoint)
|
try await makeEntityRequest(endpoint: endpoint, method: "POST")
|
||||||
let request = makeURLRequest(url: url, httpMethod: "POST")
|
}
|
||||||
let (data, httpResponse) = try await urlSession.data(for: request)
|
|
||||||
logResponseOnError(httpResponse: httpResponse, data: data)
|
public func put<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
|
||||||
return try decoder.decode(Entity.self, from: data)
|
try await makeEntityRequest(endpoint: endpoint, method: "PUT")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func delete(endpoint: Endpoint) async throws -> HTTPURLResponse? {
|
public func delete(endpoint: Endpoint) async throws -> HTTPURLResponse? {
|
||||||
|
@ -99,6 +97,14 @@ public class Client: ObservableObject, Equatable {
|
||||||
return httpResponse as? HTTPURLResponse
|
return httpResponse as? HTTPURLResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func makeEntityRequest<Entity: Decodable>(endpoint: Endpoint, method: String) async throws -> Entity {
|
||||||
|
let url = makeURL(endpoint: endpoint)
|
||||||
|
let request = makeURLRequest(url: url, httpMethod: method)
|
||||||
|
let (data, httpResponse) = try await urlSession.data(for: request)
|
||||||
|
logResponseOnError(httpResponse: httpResponse, data: data)
|
||||||
|
return try decoder.decode(Entity.self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
public func oauthURL() async throws -> URL {
|
public func oauthURL() async throws -> URL {
|
||||||
let app: InstanceApp = try await post(endpoint: Apps.registerApp)
|
let app: InstanceApp = try await post(endpoint: Apps.registerApp)
|
||||||
self.oauthApp = app
|
self.oauthApp = app
|
||||||
|
|
|
@ -5,6 +5,10 @@ public enum Statuses: Endpoint {
|
||||||
inReplyTo: String?,
|
inReplyTo: String?,
|
||||||
mediaIds: [String]?,
|
mediaIds: [String]?,
|
||||||
spoilerText: String?)
|
spoilerText: String?)
|
||||||
|
case editStatus(id: String,
|
||||||
|
status: String,
|
||||||
|
mediaIds: [String]?,
|
||||||
|
spoilerText: String?)
|
||||||
case status(id: String)
|
case status(id: String)
|
||||||
case context(id: String)
|
case context(id: String)
|
||||||
case favourite(id: String)
|
case favourite(id: String)
|
||||||
|
@ -20,6 +24,8 @@ public enum Statuses: Endpoint {
|
||||||
return "statuses"
|
return "statuses"
|
||||||
case .status(let id):
|
case .status(let id):
|
||||||
return "statuses/\(id)"
|
return "statuses/\(id)"
|
||||||
|
case .editStatus(let id, _, _, _):
|
||||||
|
return "statuses/\(id)"
|
||||||
case .context(let id):
|
case .context(let id):
|
||||||
return "statuses/\(id)/context"
|
return "statuses/\(id)/context"
|
||||||
case .favourite(let id):
|
case .favourite(let id):
|
||||||
|
@ -53,6 +59,17 @@ public enum Statuses: Endpoint {
|
||||||
params.append(.init(name: "spoiler_text", value: spoilerText))
|
params.append(.init(name: "spoiler_text", value: spoilerText))
|
||||||
}
|
}
|
||||||
return params
|
return params
|
||||||
|
case let .editStatus(_, status, mediaIds, spoilerText):
|
||||||
|
var params: [URLQueryItem] = [.init(name: "status", value: status)]
|
||||||
|
if let mediaIds {
|
||||||
|
for mediaId in mediaIds {
|
||||||
|
params.append(.init(name: "media_ids[]", value: mediaId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let spoilerText {
|
||||||
|
params.append(.init(name: "spoiler_text", value: spoilerText))
|
||||||
|
}
|
||||||
|
return params
|
||||||
case let .rebloggedBy(_, maxId):
|
case let .rebloggedBy(_, maxId):
|
||||||
return makePaginationParam(sinceId: nil, maxId: maxId)
|
return makePaginationParam(sinceId: nil, maxId: maxId)
|
||||||
case let .favouritedBy(_, maxId):
|
case let .favouritedBy(_, maxId):
|
||||||
|
|
|
@ -18,7 +18,7 @@ let package = Package(
|
||||||
.package(name: "Network", path: "../Network"),
|
.package(name: "Network", path: "../Network"),
|
||||||
.package(name: "Env", path: "../Env"),
|
.package(name: "Env", path: "../Env"),
|
||||||
.package(name: "DesignSystem", path: "../DesignSystem"),
|
.package(name: "DesignSystem", path: "../DesignSystem"),
|
||||||
.package(url: "https://github.com/Dimillian/TextView", branch: "main")
|
.package(url: "https://github.com/Dimillian/TextView", branch: "main"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
@ -28,7 +28,7 @@ let package = Package(
|
||||||
.product(name: "Network", package: "Network"),
|
.product(name: "Network", package: "Network"),
|
||||||
.product(name: "Env", package: "Env"),
|
.product(name: "Env", package: "Env"),
|
||||||
.product(name: "DesignSystem", package: "DesignSystem"),
|
.product(name: "DesignSystem", package: "DesignSystem"),
|
||||||
.product(name: "TextView", package: "TextView")
|
.product(name: "TextView", package: "TextView"),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,8 +14,8 @@ public struct StatusEditorView: View {
|
||||||
|
|
||||||
@StateObject private var viewModel: StatusEditorViewModel
|
@StateObject private var viewModel: StatusEditorViewModel
|
||||||
|
|
||||||
public init(inReplyTo: Status?) {
|
public init(mode: StatusEditorViewModel.Mode) {
|
||||||
_viewModel = StateObject(wrappedValue: .init(inReplyTo: inReplyTo))
|
_viewModel = StateObject(wrappedValue: .init(mode: mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
@ -33,10 +33,10 @@ public struct StatusEditorView: View {
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.client = client
|
viewModel.client = client
|
||||||
viewModel.insertReplyTo()
|
viewModel.prepareStatusText()
|
||||||
}
|
}
|
||||||
.padding(.horizontal, DS.Constants.layoutPadding)
|
.padding(.horizontal, DS.Constants.layoutPadding)
|
||||||
.navigationTitle("New post")
|
.navigationTitle(viewModel.mode.title)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
|
|
@ -5,7 +5,35 @@ import Network
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class StatusEditorViewModel: ObservableObject {
|
public class StatusEditorViewModel: ObservableObject {
|
||||||
|
public enum Mode {
|
||||||
|
case replyTo(status: Status)
|
||||||
|
case new
|
||||||
|
case edit(status: Status)
|
||||||
|
|
||||||
|
var replyToStatus: Status? {
|
||||||
|
switch self {
|
||||||
|
case let .replyTo(status):
|
||||||
|
return status
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .new:
|
||||||
|
return "New Post"
|
||||||
|
case .edit:
|
||||||
|
return "Edit your post"
|
||||||
|
case let .replyTo(status):
|
||||||
|
return "Reply to \(status.account.displayName)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode: Mode
|
||||||
|
|
||||||
@Published var statusText = NSAttributedString(string: "") {
|
@Published var statusText = NSAttributedString(string: "") {
|
||||||
didSet {
|
didSet {
|
||||||
guard !internalUpdate else { return }
|
guard !internalUpdate else { return }
|
||||||
|
@ -28,25 +56,33 @@ class StatusEditorViewModel: ObservableObject {
|
||||||
|
|
||||||
var client: Client?
|
var client: Client?
|
||||||
private var internalUpdate: Bool = false
|
private var internalUpdate: Bool = false
|
||||||
private var inReplyTo: Status?
|
|
||||||
|
|
||||||
let generator = UINotificationFeedbackGenerator()
|
let generator = UINotificationFeedbackGenerator()
|
||||||
|
|
||||||
init(inReplyTo: Status?) {
|
init(mode: Mode) {
|
||||||
self.inReplyTo = inReplyTo
|
self.mode = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
func postStatus() async -> Status? {
|
func postStatus() async -> Status? {
|
||||||
guard let client else { return nil }
|
guard let client else { return nil }
|
||||||
do {
|
do {
|
||||||
isPosting = true
|
isPosting = true
|
||||||
let status: Status = try await client.post(endpoint: Statuses.postStatus(status: statusText.string,
|
let postStatus: Status?
|
||||||
inReplyTo: inReplyTo?.id,
|
switch mode {
|
||||||
mediaIds: nil,
|
case .new, .replyTo:
|
||||||
spoilerText: nil))
|
postStatus = try await client.post(endpoint: Statuses.postStatus(status: statusText.string,
|
||||||
|
inReplyTo: mode.replyToStatus?.id,
|
||||||
|
mediaIds: nil,
|
||||||
|
spoilerText: nil))
|
||||||
|
case let .edit(status):
|
||||||
|
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id,
|
||||||
|
status: statusText.string,
|
||||||
|
mediaIds: nil,
|
||||||
|
spoilerText: nil))
|
||||||
|
}
|
||||||
generator.notificationOccurred(.success)
|
generator.notificationOccurred(.success)
|
||||||
isPosting = false
|
isPosting = false
|
||||||
return status
|
return postStatus
|
||||||
} catch {
|
} catch {
|
||||||
isPosting = false
|
isPosting = false
|
||||||
generator.notificationOccurred(.error)
|
generator.notificationOccurred(.error)
|
||||||
|
@ -54,9 +90,14 @@ class StatusEditorViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertReplyTo() {
|
func prepareStatusText() {
|
||||||
if let inReplyTo {
|
switch mode {
|
||||||
statusText = .init(string: "@\(inReplyTo.account.acct) ")
|
case let .replyTo(status):
|
||||||
|
statusText = .init(string: "@\(status.account.acct) ")
|
||||||
|
case let .edit(status):
|
||||||
|
statusText = .init(string: status.content.asRawText)
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
||||||
case let .error(error):
|
case let .error(error):
|
||||||
Text(error.localizedDescription)
|
Text(error.localizedDescription)
|
||||||
case let .display(statuses, nextPageState):
|
case let .display(statuses, nextPageState):
|
||||||
ForEach(statuses) { status in
|
ForEach(statuses, id: \.viewId) { status in
|
||||||
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
||||||
Divider()
|
Divider()
|
||||||
.padding(.vertical, DS.Constants.dividerPadding)
|
.padding(.vertical, DS.Constants.dividerPadding)
|
||||||
|
|
|
@ -128,7 +128,7 @@ struct StatusActionsView: View {
|
||||||
generator.notificationOccurred(.success)
|
generator.notificationOccurred(.success)
|
||||||
switch action {
|
switch action {
|
||||||
case .respond:
|
case .respond:
|
||||||
routeurPath.presentedSheet = .statusEditor(replyToStatus: viewModel.status)
|
routeurPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
|
||||||
case .favourite:
|
case .favourite:
|
||||||
if viewModel.isFavourited {
|
if viewModel.isFavourited {
|
||||||
await viewModel.unFavourite()
|
await viewModel.unFavourite()
|
||||||
|
|
|
@ -152,6 +152,11 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.account?.id == viewModel.status.account.id {
|
if account.account?.id == viewModel.status.account.id {
|
||||||
|
Button {
|
||||||
|
routeurPath.presentedSheet = .editStatusEditor(status: viewModel.status)
|
||||||
|
} label: {
|
||||||
|
Label("Edit", systemImage: "pencil")
|
||||||
|
}
|
||||||
Button(role: .destructive) { Task { await viewModel.delete() } } label: {
|
Button(role: .destructive) { Task { await viewModel.delete() } } label: {
|
||||||
Label("Delete", systemImage: "trash")
|
Label("Delete", systemImage: "trash")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
|
||||||
public struct TimelineView: View {
|
public struct TimelineView: View {
|
||||||
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@EnvironmentObject private var account: CurrentAccount
|
@EnvironmentObject private var account: CurrentAccount
|
||||||
@EnvironmentObject private var watcher: StreamWatcher
|
@EnvironmentObject private var watcher: StreamWatcher
|
||||||
@EnvironmentObject private var client: Client
|
@EnvironmentObject private var client: Client
|
||||||
|
@ -53,6 +54,16 @@ public struct TimelineView: View {
|
||||||
.onChange(of: timeline) { newTimeline in
|
.onChange(of: timeline) { newTimeline in
|
||||||
viewModel.timeline = timeline
|
viewModel.timeline = timeline
|
||||||
}
|
}
|
||||||
|
.onChange(of: scenePhase, perform: { scenePhase in
|
||||||
|
switch scenePhase {
|
||||||
|
case .active:
|
||||||
|
Task {
|
||||||
|
await viewModel.fetchStatuses(userIntent: false)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -17,6 +17,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
|
||||||
Task {
|
Task {
|
||||||
if oldValue != timeline {
|
if oldValue != timeline {
|
||||||
statuses = []
|
statuses = []
|
||||||
|
pendingStatuses = []
|
||||||
}
|
}
|
||||||
await fetchStatuses(userIntent: false)
|
await fetchStatuses(userIntent: false)
|
||||||
switch timeline {
|
switch timeline {
|
||||||
|
@ -61,18 +62,22 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
|
||||||
func fetchStatuses(userIntent: Bool) async {
|
func fetchStatuses(userIntent: Bool) async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
pendingStatuses = []
|
|
||||||
if statuses.isEmpty {
|
if statuses.isEmpty {
|
||||||
|
pendingStatuses = []
|
||||||
statusesState = .loading
|
statusesState = .loading
|
||||||
statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil, maxId: nil))
|
statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil, maxId: nil))
|
||||||
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||||
} else if let first = statuses.first {
|
} else if let first = statuses.first {
|
||||||
let newStatuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: first.id, maxId: nil))
|
var newStatuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: first.id, maxId: nil))
|
||||||
if userIntent || !pendingStatusesEnabled {
|
if userIntent || !pendingStatusesEnabled {
|
||||||
|
pendingStatuses = []
|
||||||
statuses.insert(contentsOf: newStatuses, at: 0)
|
statuses.insert(contentsOf: newStatuses, at: 0)
|
||||||
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||||
} else {
|
} else {
|
||||||
pendingStatuses = newStatuses
|
newStatuses = newStatuses.filter { status in
|
||||||
|
!pendingStatuses.contains(where: { $0.id == status.id })
|
||||||
|
}
|
||||||
|
pendingStatuses.insert(contentsOf: newStatuses, at: 0)
|
||||||
pendingStatusesState = .refresh
|
pendingStatusesState = .refresh
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue