quote replies in progress
This commit is contained in:
parent
bafdbe6c8f
commit
3b7937c085
@ -11,6 +11,8 @@ struct CompactPostView: View {
|
|||||||
@State private var initialLike: Bool = false
|
@State private var initialLike: Bool = false
|
||||||
@State private var isLiked: Bool = false
|
@State private var isLiked: Bool = false
|
||||||
@State private var isReposted: Bool = false
|
@State private var isReposted: Bool = false
|
||||||
|
@State private var hasQuote: Bool = false
|
||||||
|
@State private var quoteStatus: Status? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@ -37,6 +39,13 @@ struct CompactPostView: View {
|
|||||||
isLiked = status.reblog != nil ? status.reblog!.favourited ?? false : status.favourited ?? false
|
isLiked = status.reblog != nil ? status.reblog!.favourited ?? false : status.favourited ?? false
|
||||||
initialLike = isLiked
|
initialLike = isLiked
|
||||||
isReposted = status.reblog != nil ? status.reblog!.reblogged ?? false : status.reblogged ?? false
|
isReposted = status.reblog != nil ? status.reblog!.reblogged ?? false : status.reblogged ?? false
|
||||||
|
|
||||||
|
let likeCount: Int = status.favouritesCount - (initialLike ? 1 : 0)
|
||||||
|
let incrLike: Int = isLiked ? 1 : 0
|
||||||
|
print("original: \(status.favouritesCount)\nmin1: \(likeCount)\nincr1: \(likeCount + incrLike)")
|
||||||
|
}
|
||||||
|
.task {
|
||||||
|
await loadEmbeddedStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +133,16 @@ struct CompactPostView: View {
|
|||||||
if status.card != nil {
|
if status.card != nil {
|
||||||
PostCardView(card: status.card!)
|
PostCardView(card: status.card!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if hasQuote {
|
||||||
|
// if quoteStatus != nil {
|
||||||
|
// //TODO: Fix profile picture and stats
|
||||||
|
// QuotePostView(status: quoteStatus!)
|
||||||
|
// } else {
|
||||||
|
// ProgressView()
|
||||||
|
// .progressViewStyle(.circular)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: Action buttons
|
//MARK: Action buttons
|
||||||
@ -220,12 +239,12 @@ struct CompactPostView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status.favouritesCount > 0 || isLiked {
|
if status.favouritesCount > 0 || isLiked {
|
||||||
let i: Int = status.favouritesCount
|
let likeCount: Int = status.favouritesCount - (initialLike ? 1 : 0)
|
||||||
let favsCount: Int = i - (initialLike ? 1 : 0) + (isLiked ? 1 : 0)
|
let incrLike: Int = isLiked ? 1 : 0
|
||||||
Text("status.favourites-\(favsCount)")
|
Text("status.favourites-\(likeCount + incrLike)")
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
.foregroundStyle(.gray)
|
.foregroundStyle(.gray)
|
||||||
.contentTransition(.numericText(value: Double(favsCount)))
|
.contentTransition(.numericText(value: Double(likeCount + incrLike)))
|
||||||
.transaction { t in
|
.transaction { t in
|
||||||
t.animation = .default
|
t.animation = .default
|
||||||
}
|
}
|
||||||
@ -245,11 +264,12 @@ struct CompactPostView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status.reblog!.favouritesCount > 0 || isLiked {
|
if status.reblog!.favouritesCount > 0 || isLiked {
|
||||||
let favsCount: Int = (status.favouritesCount - (initialLike ? 1 : 0)) + (isLiked ? 1 : 0)
|
let likeCount: Int = status.reblog!.favouritesCount - (initialLike ? 1 : 0)
|
||||||
Text("status.favourites-\(favsCount)")
|
let incrLike: Int = isLiked ? 1 : 0
|
||||||
|
Text("status.favourites-\(likeCount + incrLike)")
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
.foregroundStyle(.gray)
|
.foregroundStyle(.gray)
|
||||||
.contentTransition(.numericText(value: Double(favsCount)))
|
.contentTransition(.numericText(value: Double(likeCount + incrLike)))
|
||||||
.transaction { t in
|
.transaction { t in
|
||||||
t.animation = .default
|
t.animation = .default
|
||||||
}
|
}
|
||||||
@ -258,6 +278,33 @@ struct CompactPostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func embededStatusURL() -> URL? {
|
||||||
|
let content = status.content
|
||||||
|
if let client = accountManager.getClient() {
|
||||||
|
if !content.statusesURLs.isEmpty, let url = content.statusesURLs.first, client.hasConnection(with: url) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadEmbeddedStatus() async {
|
||||||
|
guard let url = embededStatusURL(), let client = accountManager.getClient() else { hasQuote = false; return }
|
||||||
|
|
||||||
|
do {
|
||||||
|
hasQuote = true
|
||||||
|
if url.absoluteString.contains(client.server), let id = Int(url.lastPathComponent) {
|
||||||
|
quoteStatus = try await client.get(endpoint: Statuses.status(id: String(id)))
|
||||||
|
} else {
|
||||||
|
let results: SearchResults = try await client.get(endpoint: Search.search(query: url.absoluteString, type: "statuses", offset: 0, following: nil), forceVersion: .v2)
|
||||||
|
quoteStatus = results.statuses.first
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
hasQuote = false
|
||||||
|
quoteStatus = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func actionButton(_ image: String, action: @escaping () -> Void) -> some View {
|
func actionButton(_ image: String, action: @escaping () -> Void) -> some View {
|
||||||
Button {
|
Button {
|
||||||
|
@ -3,11 +3,123 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct QuotePostView: View {
|
struct QuotePostView: View {
|
||||||
|
@Environment(Navigator.self) private var navigator: Navigator
|
||||||
|
var status: Status
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
statusPost(status)
|
||||||
|
.frame(width: 250)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.clipShape(.rect(cornerRadius: 15))
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 15)
|
||||||
|
.stroke(.gray.opacity(0.3), lineWidth: 1)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if UIApplication.shared.canOpenURL(URL(string: status.url ?? .fallbackUrl)!) {
|
||||||
|
UIApplication.shared.open(URL(string: status.url ?? .fallbackUrl)!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func statusPost(_ status: AnyStatus) -> some View {
|
||||||
|
HStack(alignment: .top, spacing: 0) {
|
||||||
|
// MARK: Profile picture
|
||||||
|
if status.repliesCount > 0 {
|
||||||
|
VStack {
|
||||||
|
profilePicture
|
||||||
|
.onTapGesture {
|
||||||
|
navigator.navigate(to: .account(acc: status.account))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.gray.opacity(0.3))
|
||||||
|
.frame(width: 2.5)
|
||||||
|
.clipShape(.capsule)
|
||||||
|
.padding([.vertical], 5)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "person.crop.circle")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 15, height: 15)
|
||||||
|
.symbolRenderingMode(.monochrome)
|
||||||
|
.foregroundStyle(Color.gray.opacity(0.3))
|
||||||
|
.padding(.bottom, 2.5)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
profilePicture
|
||||||
|
.onTapGesture {
|
||||||
|
navigator.navigate(to: .account(acc: status.account))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
// MARK: Status main content
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
Text(status.account.username)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.bold()
|
||||||
|
.onTapGesture {
|
||||||
|
navigator.navigate(to: .account(acc: status.account))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !status.content.asRawText.isEmpty {
|
||||||
|
TextEmoji(status.content, emojis: status.emojis, language: status.language)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.frame(width: 250, alignment: .topLeading)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.font(.callout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.card != nil {
|
||||||
|
PostCardView(card: status.card!, inQuote: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
|
||||||
|
// MARK: Status stats
|
||||||
|
stats
|
||||||
|
.padding(.top, 5)
|
||||||
|
.padding(.bottom, status.repliesCount > 0 || status.favouritesCount > 0 ? 10 : 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var profilePicture: some View {
|
||||||
|
OnlineImage(url: status.account.avatar, size: 40, useNuke: true)
|
||||||
|
.frame(width: 25, height: 25)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.clipShape(.circle)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats: some View {
|
||||||
|
//TODO: Put this in its own view (maybe?)
|
||||||
|
HStack {
|
||||||
|
if status.repliesCount > 0 {
|
||||||
|
Text("status.replies-\(status.repliesCount)")
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.repliesCount > 0 && status.favouritesCount > 0 {
|
||||||
|
Text("•")
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.favouritesCount > 0 {
|
||||||
|
Text("status.favourites-\(status.favouritesCount)")
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
private extension String {
|
||||||
QuotePostView()
|
static let fallbackUrl = "https://joinmastodon.org/"
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,53 @@
|
|||||||
//Made by Lumaa
|
//Made by Lumaa
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
public struct SearchResults: Decodable {
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case accounts, statuses, hashtags
|
||||||
|
}
|
||||||
|
|
||||||
|
public let accounts: [Account]
|
||||||
|
public var relationships: [Relationship] = []
|
||||||
|
public let statuses: [Status]
|
||||||
|
public let hashtags: [Tag]
|
||||||
|
|
||||||
|
public var isEmpty: Bool {
|
||||||
|
accounts.isEmpty && statuses.isEmpty && hashtags.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SearchResults: Sendable {}
|
||||||
|
|
||||||
|
public enum Search: Endpoint {
|
||||||
|
case search(query: String, type: String?, offset: Int?, following: Bool?)
|
||||||
|
case accountsSearch(query: String, type: String?, offset: Int?, following: Bool?)
|
||||||
|
|
||||||
|
public func path() -> String {
|
||||||
|
switch self {
|
||||||
|
case .search:
|
||||||
|
"search"
|
||||||
|
case .accountsSearch:
|
||||||
|
"accounts/search"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
|
switch self {
|
||||||
|
case let .search(query, type, offset, following),
|
||||||
|
let .accountsSearch(query, type, offset, following):
|
||||||
|
var params: [URLQueryItem] = [.init(name: "q", value: query)]
|
||||||
|
if let type {
|
||||||
|
params.append(.init(name: "type", value: type))
|
||||||
|
}
|
||||||
|
if let offset {
|
||||||
|
params.append(.init(name: "offset", value: String(offset)))
|
||||||
|
}
|
||||||
|
if let following {
|
||||||
|
params.append(.init(name: "following", value: following ? "true" : "false"))
|
||||||
|
}
|
||||||
|
params.append(.init(name: "resolve", value: "true"))
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user