Change comments downlading

This commit is contained in:
Marcin Czachursk 2023-01-14 08:52:51 +01:00
parent 43affaa868
commit fbdb1216dc
16 changed files with 110 additions and 53 deletions

View File

@ -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;

View File

@ -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 ""
}
}

View File

@ -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)

View File

@ -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 {

View File

@ -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.

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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()
}
}

View File

@ -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())
}
}

View File

@ -45,7 +45,7 @@ struct SettingsView: View {
HStack {
Text("Version")
Spacer()
Text(appVersion ?? "")
Text(appVersion ?? String.empty())
.foregroundColor(.accentColor)
}
}

View File

@ -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)?

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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: "")
}
}