Change comments downlading
This commit is contained in:
parent
43affaa868
commit
fbdb1216dc
|
@ -75,6 +75,8 @@
|
|||
F897978D2968369600B22335 /* HapticService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897978C2968369600B22335 /* HapticService.swift */; };
|
||||
F897978F29684BCB00B22335 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897978E29684BCB00B22335 /* LoadingView.swift */; };
|
||||
F8984E4D296B648000A2610F /* UIImage+Blurhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */; };
|
||||
F898DE702972868A004B4A6A /* String+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = F898DE6F2972868A004B4A6A /* String+Empty.swift */; };
|
||||
F898DE7229728CB2004B4A6A /* CommentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F898DE7129728CB2004B4A6A /* CommentViewModel.swift */; };
|
||||
F8996DEB2971D29D0043EEC6 /* View+Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8996DEA2971D29D0043EEC6 /* View+Transition.swift */; };
|
||||
F89992C7296D3DF8005994BF /* MastodonKit in Frameworks */ = {isa = PBXBuildFile; productRef = F89992C6296D3DF8005994BF /* MastodonKit */; };
|
||||
F89992C9296D6DC7005994BF /* CommentBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89992C8296D6DC7005994BF /* CommentBody.swift */; };
|
||||
|
@ -165,6 +167,8 @@
|
|||
F897978C2968369600B22335 /* HapticService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticService.swift; sourceTree = "<group>"; };
|
||||
F897978E29684BCB00B22335 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
|
||||
F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Blurhash.swift"; sourceTree = "<group>"; };
|
||||
F898DE6F2972868A004B4A6A /* String+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Empty.swift"; sourceTree = "<group>"; };
|
||||
F898DE7129728CB2004B4A6A /* CommentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentViewModel.swift; sourceTree = "<group>"; };
|
||||
F8996DEA2971D29D0043EEC6 /* View+Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Transition.swift"; sourceTree = "<group>"; };
|
||||
F89992C8296D6DC7005994BF /* CommentBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentBody.swift; sourceTree = "<group>"; };
|
||||
F89992CB296D9231005994BF /* StatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -235,6 +239,7 @@
|
|||
F8341F8F295C636C009C8EE6 /* Data+Exif.swift */,
|
||||
F85D49862964334100751DF7 /* String+Date.swift */,
|
||||
F8C14391296AF0B3001FE31D /* String+Exif.swift */,
|
||||
F898DE6F2972868A004B4A6A /* String+Empty.swift */,
|
||||
F8210DE42966E160001D9973 /* Color+SystemColors.swift */,
|
||||
F8210DE62966E1D1001D9973 /* Color+Assets.swift */,
|
||||
F8C14393296AF21B001FE31D /* Double+Round.swift */,
|
||||
|
@ -412,6 +417,7 @@
|
|||
F89992CB296D9231005994BF /* StatusViewModel.swift */,
|
||||
F89992CD296D92E7005994BF /* AttachmentViewModel.swift */,
|
||||
F89D6C4B297197FE001DA3D4 /* ImageViewerViewModel.swift */,
|
||||
F898DE7129728CB2004B4A6A /* CommentViewModel.swift */,
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
|
@ -570,6 +576,7 @@
|
|||
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
||||
F86B721E296C458700EE59EC /* BlurredImage.swift in Sources */,
|
||||
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
|
||||
F898DE7229728CB2004B4A6A /* CommentViewModel.swift in Sources */,
|
||||
F89A46DE296EABA20062125F /* StatusPlaceholder.swift in Sources */,
|
||||
F88C2482295C3A4F0006098B /* StatusView.swift in Sources */,
|
||||
F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */,
|
||||
|
@ -603,6 +610,7 @@
|
|||
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */,
|
||||
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */,
|
||||
F85D4973296406E700751DF7 /* BottomRight.swift in Sources */,
|
||||
F898DE702972868A004B4A6A /* String+Empty.swift in Sources */,
|
||||
F86B7218296C27C100EE59EC /* ActionButton.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
static func empty() -> String {
|
||||
return ""
|
||||
}
|
||||
}
|
|
@ -8,8 +8,9 @@ import UIKit
|
|||
import SwiftUI
|
||||
|
||||
struct HTMLFormattedText: UIViewRepresentable {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
||||
let text: String
|
||||
private let text: String
|
||||
private let textView = UITextView()
|
||||
private let fontSize: Int
|
||||
private let width: Int
|
||||
|
@ -32,11 +33,11 @@ struct HTMLFormattedText: UIViewRepresentable {
|
|||
}
|
||||
|
||||
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<Self>) {
|
||||
DispatchQueue.main.async {
|
||||
Task { @MainActor in
|
||||
if let attributeText = self.converHTML(text: text) {
|
||||
textView.attributedText = attributeText
|
||||
} else {
|
||||
textView.text = ""
|
||||
textView.text = String.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,13 +54,14 @@ struct HTMLFormattedText: UIViewRepresentable {
|
|||
|
||||
let linkAttributes = [
|
||||
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
||||
NSAttributedString.Key.foregroundColor: UIColor(.accentColor)
|
||||
NSAttributedString.Key.foregroundColor: UIColor(applicationState.tintColor.color())
|
||||
]
|
||||
|
||||
if let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
|
||||
|
||||
if let attributedString = try? NSMutableAttributedString(data: data,
|
||||
options: [.documentType: NSAttributedString.DocumentType.html],
|
||||
documentAttributes: nil) {
|
||||
attributedString.enumerateAttributes(in: NSRange(0..<attributedString.length)) { value, range, stop in
|
||||
attributedString.setAttributes(largeAttributes, range: range)
|
||||
attributedString.setAttributes(largeAttributes, range: range)
|
||||
|
||||
if value.keys.contains(NSAttributedString.Key.link) {
|
||||
attributedString.setAttributes(linkAttributes, range: range)
|
||||
|
|
|
@ -15,7 +15,7 @@ public class ApplicationState: ObservableObject {
|
|||
@Published var accountData: AccountData?
|
||||
@Published var tintColor = TintColor.accentColor2
|
||||
@Published var theme = Theme.system
|
||||
@Published var showInteractionStatusId = ""
|
||||
@Published var showInteractionStatusId = String.empty()
|
||||
}
|
||||
|
||||
extension ApplicationState {
|
||||
|
|
|
@ -84,7 +84,7 @@ public class AuthorizationService {
|
|||
accountData.serverUrl = baseUrl
|
||||
accountData.clientId = oAuthApp.clientId
|
||||
accountData.clientSecret = oAuthApp.clientSecret
|
||||
accountData.clientVapidKey = oAuthApp.vapidKey ?? ""
|
||||
accountData.clientVapidKey = oAuthApp.vapidKey ?? String.empty()
|
||||
accountData.accessToken = oAuthSwiftCredential.oauthToken
|
||||
|
||||
// Download avatar image.
|
||||
|
|
|
@ -49,9 +49,31 @@ public class TimelineService {
|
|||
return try await client.read(statusId: statusId)
|
||||
}
|
||||
|
||||
public func getComments(for statusId: String, and accountData: AccountData) async throws -> Context {
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? "")
|
||||
return try await client.getContext(for: statusId)
|
||||
public func getComments(for statusId: String, and accountData: AccountData) async throws -> [CommentViewModel] {
|
||||
var commentViewModels: [CommentViewModel] = []
|
||||
|
||||
// Get first level of comments.
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? String.empty())
|
||||
let context = try await client.getContext(for: statusId)
|
||||
|
||||
// Iterate throught first level of comments and download descendants/
|
||||
let descendants = context.descendants.toStatusViewModel()
|
||||
for status in descendants {
|
||||
commentViewModels.append(CommentViewModel(status: status, showDivider: true))
|
||||
try await self.getCommentDescendants(for: status.id, client: client, to: &commentViewModels)
|
||||
}
|
||||
|
||||
return commentViewModels
|
||||
}
|
||||
|
||||
private func getCommentDescendants(for statusId: String, client: MastodonClientAuthenticated, to commentViewModels: inout [CommentViewModel]) async throws {
|
||||
let context = try await client.getContext(for: statusId)
|
||||
|
||||
let descendants = context.descendants.toStatusViewModel()
|
||||
for status in descendants {
|
||||
commentViewModels.append(CommentViewModel(status: status, showDivider: false))
|
||||
try await self.getCommentDescendants(for: status.id, client: client, to: &commentViewModels)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData(for accountData: AccountData, on backgroundContext: NSManagedObjectContext, minId: String? = nil, maxId: String? = nil) async throws -> Int {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct CommentViewModel {
|
||||
var status: StatusViewModel
|
||||
var showDivider: Bool
|
||||
}
|
|
@ -152,8 +152,12 @@ public extension StatusViewModel {
|
|||
|
||||
public extension [Status] {
|
||||
func toStatusViewModel() -> [StatusViewModel] {
|
||||
self.map { status in
|
||||
StatusViewModel(status: status)
|
||||
}
|
||||
self
|
||||
.sorted(by: { lhs, rhs in
|
||||
lhs.id < rhs.id
|
||||
})
|
||||
.map { status in
|
||||
StatusViewModel(status: status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ struct ComposeView: View {
|
|||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@Binding var statusViewModel: StatusViewModel?
|
||||
@State private var text = ""
|
||||
@State private var text = String.empty()
|
||||
|
||||
@FocusState private var focusedField: FocusField?
|
||||
|
||||
|
@ -120,7 +120,7 @@ struct ComposeView: View {
|
|||
}
|
||||
|
||||
private func getUserName(statusViewModel: StatusViewModel) -> String {
|
||||
return self.statusViewModel?.account.displayName ?? self.statusViewModel?.account.acct ?? self.statusViewModel?.account.username ?? ""
|
||||
return self.statusViewModel?.account.displayName ?? self.statusViewModel?.account.acct ?? self.statusViewModel?.account.username ?? String.empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,24 +64,24 @@ struct MainView: View {
|
|||
private func getMainView() -> some View {
|
||||
switch self.viewMode {
|
||||
case .home:
|
||||
HomeFeedView(accountId: applicationState.accountData?.id ?? "")
|
||||
.id(applicationState.accountData?.id ?? "")
|
||||
HomeFeedView(accountId: applicationState.accountData?.id ?? String.empty())
|
||||
.id(applicationState.accountData?.id ?? String.empty())
|
||||
case .local:
|
||||
LocalFeedView()
|
||||
.id(applicationState.accountData?.id ?? "")
|
||||
.id(applicationState.accountData?.id ?? String.empty())
|
||||
case .federated:
|
||||
FederatedFeedView()
|
||||
.id(applicationState.accountData?.id ?? "")
|
||||
.id(applicationState.accountData?.id ?? String.empty())
|
||||
case .profile:
|
||||
if let accountData = self.applicationState.accountData {
|
||||
UserProfileView(accountId: accountData.id,
|
||||
accountDisplayName: accountData.displayName,
|
||||
accountUserName: accountData.acct)
|
||||
.id(applicationState.accountData?.id ?? "")
|
||||
.id(applicationState.accountData?.id ?? String.empty())
|
||||
}
|
||||
case .notifications:
|
||||
NotificationsView()
|
||||
.id(applicationState.accountData?.id ?? "")
|
||||
.id(applicationState.accountData?.id ?? String.empty())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ struct SettingsView: View {
|
|||
HStack {
|
||||
Text("Version")
|
||||
Spacer()
|
||||
Text(appVersion ?? "")
|
||||
Text(appVersion ?? String.empty())
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ struct SignInView: View {
|
|||
@Environment(\.dismiss) private var dismiss
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
||||
@State private var serverAddress: String = ""
|
||||
@State private var serverAddress: String = String.empty()
|
||||
|
||||
|
||||
var onSignInStateChenge: ((_ applicationViewMode: ApplicationViewMode) -> Void)?
|
||||
|
|
|
@ -105,14 +105,8 @@ struct StatusView: View {
|
|||
.fullScreenCover(isPresented: $showImageViewer, content: {
|
||||
if let statusViewModel = self.statusViewModel {
|
||||
ImagesViewer(statusViewModel: statusViewModel)
|
||||
// ImagesViewer(statusViewModel: statusViewModel, imgViewModel: imgViewModel)
|
||||
}
|
||||
})
|
||||
// .overlay(content: {
|
||||
// if self.showImageViewer, let statusViewModel = self.statusViewModel {
|
||||
// ImagesViewer(showImageViewer: $showImageViewer, statusViewModel: statusViewModel)
|
||||
// }
|
||||
// })
|
||||
.task {
|
||||
do {
|
||||
guard firstLoadFinished == false else {
|
||||
|
@ -158,6 +152,7 @@ struct StatusView: View {
|
|||
return self.calculateHeight(width: Double(imageWidth), height: Double(imageHeight))
|
||||
}
|
||||
|
||||
// If we don't have image height and width in metadata, we have to use some constant height.
|
||||
return UIScreen.main.bounds.width * 0.75
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
struct ImagesCarousel: View {
|
||||
@State public var attachments: [AttachmentViewModel]
|
||||
@State private var height: Double = 0.0
|
||||
@State private var selectedAttachmentId = ""
|
||||
@State private var selectedAttachmentId = String.empty()
|
||||
|
||||
@Binding public var exifCamera: String?
|
||||
@Binding public var exifExposure: String?
|
||||
|
@ -38,7 +38,7 @@ struct ImagesCarousel: View {
|
|||
}
|
||||
})
|
||||
.onAppear {
|
||||
self.selectedAttachmentId = self.attachments.first?.id ?? ""
|
||||
self.selectedAttachmentId = self.attachments.first?.id ?? String.empty()
|
||||
self.calculateImageHeight()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ struct CommentBody: View {
|
|||
.onTapGesture {
|
||||
withAnimation(.linear(duration: 0.3)) {
|
||||
if self.statusViewModel.id == self.applicationState.showInteractionStatusId {
|
||||
self.applicationState.showInteractionStatusId = ""
|
||||
self.applicationState.showInteractionStatusId = String.empty()
|
||||
} else {
|
||||
self.applicationState.showInteractionStatusId = self.statusViewModel.id
|
||||
}
|
||||
|
|
|
@ -12,29 +12,29 @@ struct CommentsSection: View {
|
|||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
||||
@State public var statusId: String
|
||||
@State public var withDivider = true
|
||||
@State private var context: Context?
|
||||
|
||||
var onNewStatus: ((_ context: StatusViewModel) -> Void)?
|
||||
|
||||
@State private var commentViewModels: [CommentViewModel]?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if let context = context {
|
||||
ForEach(context.descendants.toStatusViewModel(), id: \.id) { statusViewModel in
|
||||
if let commentViewModels {
|
||||
ForEach(commentViewModels, id: \.status.id) { commentViewModel in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
|
||||
if withDivider {
|
||||
if commentViewModel.showDivider {
|
||||
Divider()
|
||||
.foregroundColor(.mainTextColor)
|
||||
.frame(height: 1)
|
||||
.overlay(Color.placeholderText.opacity(0.3))
|
||||
.padding(0)
|
||||
}
|
||||
|
||||
CommentBody(statusViewModel: statusViewModel)
|
||||
CommentBody(statusViewModel: commentViewModel.status)
|
||||
|
||||
if self.applicationState.showInteractionStatusId == statusViewModel.id {
|
||||
if self.applicationState.showInteractionStatusId == commentViewModel.status.id {
|
||||
VStack (alignment: .leading, spacing: 0) {
|
||||
InteractionRow(statusViewModel: statusViewModel) {
|
||||
self.onNewStatus?(statusViewModel)
|
||||
InteractionRow(statusViewModel: commentViewModel.status) {
|
||||
self.onNewStatus?(commentViewModel.status)
|
||||
}
|
||||
.foregroundColor(self.getInteractionRowTextColor())
|
||||
.padding(.horizontal, 16)
|
||||
|
@ -43,20 +43,20 @@ struct CommentsSection: View {
|
|||
.background(Color.lightGrayColor.opacity(0.5))
|
||||
.transition(AnyTransition.move(edge: .top).combined(with: .opacity))
|
||||
}
|
||||
|
||||
CommentsSection(statusId: statusViewModel.id, withDivider: false) { context in
|
||||
self.onNewStatus?(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
do {
|
||||
if let accountData = applicationState.accountData {
|
||||
self.context = try await TimelineService.shared.getComments(
|
||||
for: statusId,
|
||||
and: accountData)
|
||||
self.commentViewModels = try await TimelineService.shared.getComments(for: statusId, and: accountData)
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
|
@ -71,6 +71,6 @@ struct CommentsSection: View {
|
|||
|
||||
struct CommentsSection_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CommentsSection(statusId: "", withDivider: true)
|
||||
CommentsSection(statusId: "")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue