mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-09-03 00:23:49 +00:00
Do string manipulation at the decode time to keep the UI smooth fix #178
This commit is contained in:
parent
7a0b635033
commit
d1034cd9a3
19 changed files with 87 additions and 83 deletions
|
@ -106,7 +106,7 @@ struct AccountDetailHeaderView: View {
|
||||||
accountAvatarView
|
accountAvatarView
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
EmojiTextApp(account.safeDisplayName.asMarkdown, emojis: account.emojis)
|
EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis)
|
||||||
.font(.scaledHeadline)
|
.font(.scaledHeadline)
|
||||||
Text("@\(account.acct)")
|
Text("@\(account.acct)")
|
||||||
.font(.scaledCallout)
|
.font(.scaledCallout)
|
||||||
|
@ -121,7 +121,7 @@ struct AccountDetailHeaderView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EmojiTextApp(account.note.asMarkdown, emojis: account.emojis)
|
EmojiTextApp(account.note, emojis: account.emojis)
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
|
|
|
@ -242,7 +242,7 @@ public struct AccountDetailView: View {
|
||||||
Image(systemName: "checkmark.seal")
|
Image(systemName: "checkmark.seal")
|
||||||
.foregroundColor(Color.green.opacity(0.80))
|
.foregroundColor(Color.green.opacity(0.80))
|
||||||
}
|
}
|
||||||
EmojiTextApp(field.value.asMarkdown, emojis: viewModel.account?.emojis ?? [])
|
EmojiTextApp(field.value, emojis: viewModel.account?.emojis ?? [])
|
||||||
.foregroundColor(theme.tintColor)
|
.foregroundColor(theme.tintColor)
|
||||||
}
|
}
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
|
@ -360,7 +360,7 @@ public struct AccountDetailView: View {
|
||||||
if scrollOffset < -200 {
|
if scrollOffset < -200 {
|
||||||
switch viewModel.accountState {
|
switch viewModel.accountState {
|
||||||
case let .data(account):
|
case let .data(account):
|
||||||
EmojiTextApp(account.safeDisplayName.asMarkdown, emojis: account.emojis)
|
EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis)
|
||||||
.font(.scaledHeadline)
|
.font(.scaledHeadline)
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
|
|
@ -33,13 +33,13 @@ public struct AccountsListRow: View {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
AvatarView(url: viewModel.account.avatar, size: .status)
|
AvatarView(url: viewModel.account.avatar, size: .status)
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
EmojiTextApp(viewModel.account.safeDisplayName.asMarkdown, emojis: viewModel.account.emojis)
|
EmojiTextApp(.init(stringValue: viewModel.account.safeDisplayName), emojis: viewModel.account.emojis)
|
||||||
.font(.scaledSubheadline)
|
.font(.scaledSubheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
Text("@\(viewModel.account.acct)")
|
Text("@\(viewModel.account.acct)")
|
||||||
.font(.scaledFootnote)
|
.font(.scaledFootnote)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
EmojiTextApp(viewModel.account.note.asMarkdown, emojis: viewModel.account.emojis)
|
EmojiTextApp(viewModel.account.note, emojis: viewModel.account.emojis)
|
||||||
.font(.scaledFootnote)
|
.font(.scaledFootnote)
|
||||||
.lineLimit(3)
|
.lineLimit(3)
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
|
|
|
@ -50,7 +50,7 @@ public struct AppAccountView: View {
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if let account = viewModel.account {
|
if let account = viewModel.account {
|
||||||
EmojiTextApp(account.safeDisplayName.asMarkdown, emojis: account.emojis)
|
EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis)
|
||||||
Text("\(account.username)@\(viewModel.appAccount.server)")
|
Text("\(account.username)@\(viewModel.appAccount.server)")
|
||||||
.font(.scaledSubheadline)
|
.font(.scaledSubheadline)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|
|
@ -19,7 +19,7 @@ struct ConversationsListRow: View {
|
||||||
AvatarView(url: conversation.accounts.first!.avatar)
|
AvatarView(url: conversation.accounts.first!.avatar)
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
HStack {
|
HStack {
|
||||||
EmojiTextApp(conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", "),
|
EmojiTextApp(.init(stringValue: conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", ")),
|
||||||
emojis: conversation.accounts.flatMap{ $0.emojis })
|
emojis: conversation.accounts.flatMap{ $0.emojis })
|
||||||
.font(.scaledSubheadline)
|
.font(.scaledSubheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Models
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct EmojiTextApp: View {
|
public struct EmojiTextApp: View {
|
||||||
private let markdown: String
|
private let markdown: HTMLString
|
||||||
private let emojis: [any CustomEmoji]
|
private let emojis: [any CustomEmoji]
|
||||||
private let append: (() -> Text)?
|
private let append: (() -> Text)?
|
||||||
|
|
||||||
|
@ -17,14 +17,14 @@ public struct EmojiTextApp: View {
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
if let append {
|
if let append {
|
||||||
EmojiText(markdown: markdown, emojis: emojis)
|
EmojiText(markdown: markdown.asMarkdown, emojis: emojis)
|
||||||
.append {
|
.append {
|
||||||
append()
|
append()
|
||||||
}
|
}
|
||||||
} else if emojis.isEmpty {
|
} else if emojis.isEmpty {
|
||||||
Text(markdown.asSafeAttributedString)
|
Text(markdown.asSafeMarkdownAttributedString)
|
||||||
} else {
|
} else {
|
||||||
EmojiText(markdown: markdown, emojis: emojis)
|
EmojiText(markdown: markdown.asMarkdown, emojis: emojis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ public struct ListEditView: View {
|
||||||
HStack {
|
HStack {
|
||||||
AvatarView(url: account.avatar, size: .status)
|
AvatarView(url: account.avatar, size: .status)
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
EmojiTextApp(account.safeDisplayName.asMarkdown,
|
EmojiTextApp(.init(stringValue: account.safeDisplayName),
|
||||||
emojis: account.emojis)
|
emojis: account.emojis)
|
||||||
Text("@\(account.acct)")
|
Text("@\(account.acct)")
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Account: Codable, Identifiable, Equatable, Hashable {
|
public struct Account: Decodable, Identifiable, Equatable, Hashable {
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(id)
|
hasher.combine(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Field: Codable, Equatable, Identifiable {
|
public struct Field: Decodable, Equatable, Identifiable {
|
||||||
public var id: String {
|
public var id: String {
|
||||||
value + name
|
value.asRawText + name
|
||||||
}
|
}
|
||||||
|
|
||||||
public let name: String
|
public let name: String
|
||||||
|
@ -15,7 +15,7 @@ public struct Account: Codable, Identifiable, Equatable, Hashable {
|
||||||
public let verifiedAt: String?
|
public let verifiedAt: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Source: Codable, Equatable {
|
public struct Source: Decodable, Equatable {
|
||||||
public let privacy: Visibility
|
public let privacy: Visibility
|
||||||
public let sensitive: Bool
|
public let sensitive: Bool
|
||||||
public let language: String?
|
public let language: String?
|
||||||
|
@ -50,7 +50,7 @@ public struct Account: Codable, Identifiable, Equatable, Hashable {
|
||||||
avatar: URL(string: "https://files.mastodon.social/media_attachments/files/003/134/405/original/04060b07ddf7bb0b.png")!,
|
avatar: URL(string: "https://files.mastodon.social/media_attachments/files/003/134/405/original/04060b07ddf7bb0b.png")!,
|
||||||
header: URL(string: "https://files.mastodon.social/media_attachments/files/003/134/405/original/04060b07ddf7bb0b.png")!,
|
header: URL(string: "https://files.mastodon.social/media_attachments/files/003/134/405/original/04060b07ddf7bb0b.png")!,
|
||||||
acct: "account@account.com",
|
acct: "account@account.com",
|
||||||
note: "Some content",
|
note: .init(stringValue: "Some content"),
|
||||||
createdAt: "2022-12-16T10:20:54.000Z",
|
createdAt: "2022-12-16T10:20:54.000Z",
|
||||||
followersCount: 10,
|
followersCount: 10,
|
||||||
followingCount: 10,
|
followingCount: 10,
|
||||||
|
@ -71,7 +71,7 @@ public struct Account: Codable, Identifiable, Equatable, Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct FamiliarAccounts: Codable {
|
public struct FamiliarAccounts: Decodable {
|
||||||
public let id: String
|
public let id: String
|
||||||
public let accounts: [Account]
|
public let accounts: [Account]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,55 +3,58 @@ import HTML2Markdown
|
||||||
import SwiftSoup
|
import SwiftSoup
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public typealias HTMLString = String
|
public struct HTMLString: Decodable, Equatable {
|
||||||
|
public let htmlValue: String
|
||||||
public extension HTMLString {
|
public let asMarkdown: String
|
||||||
var asMarkdown: String {
|
public let asRawText: String
|
||||||
|
public let statusesURLs: [URL]
|
||||||
|
public let asSafeMarkdownAttributedString: AttributedString
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
htmlValue = try container.decode(String.self)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let dom = try HTMLParser().parse(html: self)
|
asMarkdown = try HTMLParser().parse(html: htmlValue)
|
||||||
return dom.toMarkdown()
|
.toMarkdown()
|
||||||
// Add space between hashtags and mentions that follow each other
|
|
||||||
.replacingOccurrences(of: ")[", with: ") [")
|
.replacingOccurrences(of: ")[", with: ") [")
|
||||||
} catch {
|
} catch {
|
||||||
return self
|
asMarkdown = htmlValue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
var statusesURLs: [URL] = []
|
||||||
var asRawText: String {
|
|
||||||
do {
|
do {
|
||||||
let document: Document = try SwiftSoup.parse(self)
|
let document: Document = try SwiftSoup.parse(htmlValue)
|
||||||
return try document.text()
|
|
||||||
} catch {
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findStatusesURLs() -> [URL]? {
|
|
||||||
do {
|
|
||||||
let document: Document = try SwiftSoup.parse(self)
|
|
||||||
let links: Elements = try document.select("a")
|
let links: Elements = try document.select("a")
|
||||||
var URLs: [URL] = []
|
|
||||||
for link in links {
|
for link in links {
|
||||||
let href = try link.attr("href")
|
let href = try link.attr("href")
|
||||||
if let url = URL(string: href),
|
if let url = URL(string: href),
|
||||||
let _ = Int(url.lastPathComponent)
|
let _ = Int(url.lastPathComponent)
|
||||||
{
|
{
|
||||||
URLs.append(url)
|
statusesURLs.append(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return URLs
|
asRawText = try document.text()
|
||||||
} catch {
|
} catch {
|
||||||
return nil
|
asRawText = htmlValue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
self.statusesURLs = statusesURLs
|
||||||
var asSafeAttributedString: AttributedString {
|
|
||||||
do {
|
do {
|
||||||
let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true,
|
let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true,
|
||||||
interpretedSyntax: .inlineOnlyPreservingWhitespace)
|
interpretedSyntax: .inlineOnlyPreservingWhitespace)
|
||||||
return try AttributedString(markdown: self, options: options)
|
asSafeMarkdownAttributedString = try AttributedString(markdown: asMarkdown, options: options)
|
||||||
} catch {
|
} catch {
|
||||||
return AttributedString(stringLiteral: self)
|
asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(stringValue: String) {
|
||||||
|
htmlValue = stringValue
|
||||||
|
asMarkdown = stringValue
|
||||||
|
asRawText = stringValue
|
||||||
|
statusesURLs = []
|
||||||
|
asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,33 +3,34 @@ import Foundation
|
||||||
public typealias ServerDate = String
|
public typealias ServerDate = String
|
||||||
|
|
||||||
extension ServerDate {
|
extension ServerDate {
|
||||||
private static var createdAtDateFormatter: DateFormatter {
|
private static var createdAtDateFormatter: DateFormatter = {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.calendar = .init(identifier: .iso8601)
|
dateFormatter.calendar = .init(identifier: .iso8601)
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
|
||||||
dateFormatter.timeZone = .init(abbreviation: "UTC")
|
dateFormatter.timeZone = .init(abbreviation: "UTC")
|
||||||
return dateFormatter
|
return dateFormatter
|
||||||
}
|
}()
|
||||||
|
|
||||||
private static var createdAtRelativeFormatter: RelativeDateTimeFormatter {
|
private static var createdAtRelativeFormatter: RelativeDateTimeFormatter = {
|
||||||
let dateFormatter = RelativeDateTimeFormatter()
|
let dateFormatter = RelativeDateTimeFormatter()
|
||||||
dateFormatter.unitsStyle = .abbreviated
|
dateFormatter.unitsStyle = .abbreviated
|
||||||
return dateFormatter
|
return dateFormatter
|
||||||
}
|
}()
|
||||||
|
|
||||||
private static var createdAtShortDateFormatted: DateFormatter {
|
private static var createdAtShortDateFormatted: DateFormatter = {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateStyle = .medium
|
dateFormatter.dateStyle = .medium
|
||||||
return dateFormatter
|
return dateFormatter
|
||||||
}
|
}()
|
||||||
|
|
||||||
|
private static let calendar = Calendar(identifier: .gregorian)
|
||||||
|
|
||||||
public var asDate: Date {
|
public var asDate: Date {
|
||||||
Self.createdAtDateFormatter.date(from: self)!
|
Self.createdAtDateFormatter.date(from: self)!
|
||||||
}
|
}
|
||||||
|
|
||||||
public var formatted: String {
|
public var formatted: String {
|
||||||
let calendar = Calendar(identifier: .gregorian)
|
if Self.calendar.numberOfDaysBetween(asDate, and: Date()) > 1 {
|
||||||
if calendar.numberOfDaysBetween(asDate, and: Date()) > 1 {
|
|
||||||
return Self.createdAtShortDateFormatted.string(from: asDate)
|
return Self.createdAtShortDateFormatted.string(from: asDate)
|
||||||
} else {
|
} else {
|
||||||
return Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date())
|
return Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date())
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Notification: Codable, Identifiable {
|
public struct Notification: Decodable, Identifiable {
|
||||||
public enum NotificationType: String, CaseIterable {
|
public enum NotificationType: String, CaseIterable {
|
||||||
case follow, follow_request, mention, reblog, status, favourite, poll, update
|
case follow, follow_request, mention, reblog, status, favourite, poll, update
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,13 +48,13 @@ public protocol AnyStatus {
|
||||||
var inReplyToAccountId: String? { get }
|
var inReplyToAccountId: String? { get }
|
||||||
var visibility: Visibility { get }
|
var visibility: Visibility { get }
|
||||||
var poll: Poll? { get }
|
var poll: Poll? { get }
|
||||||
var spoilerText: String { get }
|
var spoilerText: HTMLString { get }
|
||||||
var filtered: [Filtered]? { get }
|
var filtered: [Filtered]? { get }
|
||||||
var sensitive: Bool { get }
|
var sensitive: Bool { get }
|
||||||
var language: String? { get }
|
var language: String? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Status: AnyStatus, Codable, Identifiable {
|
public struct Status: AnyStatus, Decodable, Identifiable {
|
||||||
public var viewId: String {
|
public var viewId: String {
|
||||||
id + createdAt + (editedAt ?? "")
|
id + createdAt + (editedAt ?? "")
|
||||||
}
|
}
|
||||||
|
@ -81,14 +81,14 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
||||||
public let inReplyToAccountId: String?
|
public let inReplyToAccountId: String?
|
||||||
public let visibility: Visibility
|
public let visibility: Visibility
|
||||||
public let poll: Poll?
|
public let poll: Poll?
|
||||||
public let spoilerText: String
|
public let spoilerText: HTMLString
|
||||||
public let filtered: [Filtered]?
|
public let filtered: [Filtered]?
|
||||||
public let sensitive: Bool
|
public let sensitive: Bool
|
||||||
public let language: String?
|
public let language: String?
|
||||||
|
|
||||||
public static func placeholder() -> Status {
|
public static func placeholder() -> Status {
|
||||||
.init(id: UUID().uuidString,
|
.init(id: UUID().uuidString,
|
||||||
content: "This is a #toot\nWith some @content\nAnd some more content for your #eyes @only",
|
content: .init(stringValue: "This is a #toot\nWith some @content\nAnd some more content for your #eyes @only"),
|
||||||
account: .placeholder(),
|
account: .placeholder(),
|
||||||
createdAt: "2022-12-16T10:20:54.000Z",
|
createdAt: "2022-12-16T10:20:54.000Z",
|
||||||
editedAt: nil,
|
editedAt: nil,
|
||||||
|
@ -109,7 +109,7 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
||||||
inReplyToAccountId: nil,
|
inReplyToAccountId: nil,
|
||||||
visibility: .pub,
|
visibility: .pub,
|
||||||
poll: nil,
|
poll: nil,
|
||||||
spoilerText: "",
|
spoilerText: .init(stringValue: ""),
|
||||||
filtered: [],
|
filtered: [],
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
language: nil)
|
language: nil)
|
||||||
|
@ -120,13 +120,13 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ReblogStatus: AnyStatus, Codable, Identifiable {
|
public struct ReblogStatus: AnyStatus, Decodable, Identifiable {
|
||||||
public var viewId: String {
|
public var viewId: String {
|
||||||
id + createdAt + (editedAt ?? "")
|
id + createdAt + (editedAt ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
public let id: String
|
public let id: String
|
||||||
public let content: String
|
public let content: HTMLString
|
||||||
public let account: Account
|
public let account: Account
|
||||||
public let createdAt: String
|
public let createdAt: String
|
||||||
public let editedAt: ServerDate?
|
public let editedAt: ServerDate?
|
||||||
|
@ -146,7 +146,7 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable {
|
||||||
public let inReplyToAccountId: String?
|
public let inReplyToAccountId: String?
|
||||||
public let visibility: Visibility
|
public let visibility: Visibility
|
||||||
public let poll: Poll?
|
public let poll: Poll?
|
||||||
public let spoilerText: String
|
public let spoilerText: HTMLString
|
||||||
public let filtered: [Filtered]?
|
public let filtered: [Filtered]?
|
||||||
public let sensitive: Bool
|
public let sensitive: Bool
|
||||||
public let language: String?
|
public let language: String?
|
||||||
|
|
|
@ -51,7 +51,7 @@ struct NotificationRowView: View {
|
||||||
private func makeMainLabel(type: Models.Notification.NotificationType) -> some View {
|
private func makeMainLabel(type: Models.Notification.NotificationType) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
EmojiTextApp(notification.account.safeDisplayName.asMarkdown,
|
EmojiTextApp(.init(stringValue: notification.account.safeDisplayName),
|
||||||
emojis: notification.account.emojis,
|
emojis: notification.account.emojis,
|
||||||
append: {
|
append: {
|
||||||
Text(" ") +
|
Text(" ") +
|
||||||
|
@ -97,7 +97,7 @@ struct NotificationRowView: View {
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
if type == .follow {
|
if type == .follow {
|
||||||
EmojiTextApp(notification.account.note.asMarkdown,
|
EmojiTextApp(notification.account.note,
|
||||||
emojis: notification.account.emojis)
|
emojis: notification.account.emojis)
|
||||||
.lineLimit(3)
|
.lineLimit(3)
|
||||||
.font(.scaledCallout)
|
.font(.scaledCallout)
|
||||||
|
|
|
@ -32,7 +32,7 @@ struct StatusEditorAutoCompleteView: View {
|
||||||
HStack {
|
HStack {
|
||||||
AvatarView(url: account.avatar, size: .badge)
|
AvatarView(url: account.avatar, size: .badge)
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
EmojiTextApp(account.safeDisplayName.asMarkdown,
|
EmojiTextApp(.init(stringValue: account.safeDisplayName),
|
||||||
emojis: account.emojis)
|
emojis: account.emojis)
|
||||||
.font(.scaledFootnote)
|
.font(.scaledFootnote)
|
||||||
.foregroundColor(theme.labelColor)
|
.foregroundColor(theme.labelColor)
|
||||||
|
|
|
@ -177,14 +177,14 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
self.visibility = visibility
|
self.visibility = visibility
|
||||||
selectedRange = .init(location: statusText.string.utf16.count, length: 0)
|
selectedRange = .init(location: statusText.string.utf16.count, length: 0)
|
||||||
case let .edit(status):
|
case let .edit(status):
|
||||||
var rawText = NSAttributedString(status.content.asMarkdown.asSafeAttributedString).string
|
var rawText = NSAttributedString(status.content.asSafeMarkdownAttributedString).string
|
||||||
for mention in status.mentions {
|
for mention in status.mentions {
|
||||||
rawText = rawText.replacingOccurrences(of: "@\(mention.username)", with: "@\(mention.acct)")
|
rawText = rawText.replacingOccurrences(of: "@\(mention.username)", with: "@\(mention.acct)")
|
||||||
}
|
}
|
||||||
statusText = .init(string: rawText)
|
statusText = .init(string: rawText)
|
||||||
selectedRange = .init(location: statusText.string.utf16.count, length: 0)
|
selectedRange = .init(location: statusText.string.utf16.count, length: 0)
|
||||||
spoilerOn = !status.spoilerText.isEmpty
|
spoilerOn = !status.spoilerText.asRawText.isEmpty
|
||||||
spoilerText = status.spoilerText
|
spoilerText = status.spoilerText.asRawText
|
||||||
visibility = status.visibility
|
visibility = status.visibility
|
||||||
mediasImages = status.mediaAttachments.map { .init(image: nil, mediaAttachment: $0, error: nil) }
|
mediasImages = status.mediaAttachments.map { .init(image: nil, mediaAttachment: $0, error: nil) }
|
||||||
case let .quote(status):
|
case let .quote(status):
|
||||||
|
|
|
@ -35,7 +35,7 @@ public struct StatusEmbeddedView: View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
AvatarView(url: account.avatar, size: .embed)
|
AvatarView(url: account.avatar, size: .embed)
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
EmojiTextApp(status.account.safeDisplayName.asMarkdown, emojis: account.emojis)
|
EmojiTextApp(.init(stringValue: status.account.safeDisplayName), emojis: account.emojis)
|
||||||
.font(.scaledFootnote)
|
.font(.scaledFootnote)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
Group {
|
Group {
|
||||||
|
|
|
@ -24,7 +24,7 @@ public struct StatusEditHistoryView: View {
|
||||||
if let history {
|
if let history {
|
||||||
ForEach(history) { edit in
|
ForEach(history) { edit in
|
||||||
VStack(alignment: .leading, spacing: 8){
|
VStack(alignment: .leading, spacing: 8){
|
||||||
EmojiTextApp(edit.content.asMarkdown, emojis: edit.emojis)
|
EmojiTextApp(edit.content, emojis: edit.emojis)
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
Group {
|
Group {
|
||||||
Text(edit.createdAt.asDate, style: .date) +
|
Text(edit.createdAt.asDate, style: .date) +
|
||||||
|
|
|
@ -100,7 +100,7 @@ public struct StatusRowView: View {
|
||||||
Image(systemName: "arrow.left.arrow.right.circle.fill")
|
Image(systemName: "arrow.left.arrow.right.circle.fill")
|
||||||
AvatarView(url: viewModel.status.account.avatar, size: .boost)
|
AvatarView(url: viewModel.status.account.avatar, size: .boost)
|
||||||
if viewModel.status.account.username != account.account?.username {
|
if viewModel.status.account.username != account.account?.username {
|
||||||
EmojiTextApp(viewModel.status.account.safeDisplayName.asMarkdown, emojis: viewModel.status.account.emojis)
|
EmojiTextApp(.init(stringValue: viewModel.status.account.safeDisplayName), emojis: viewModel.status.account.emojis)
|
||||||
Text("status.row.was-boosted")
|
Text("status.row.was-boosted")
|
||||||
} else {
|
} else {
|
||||||
Text("status.row.you-boosted")
|
Text("status.row.you-boosted")
|
||||||
|
@ -177,8 +177,8 @@ public struct StatusRowView: View {
|
||||||
|
|
||||||
private func makeStatusContentView(status: AnyStatus) -> some View {
|
private func makeStatusContentView(status: AnyStatus) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if !status.spoilerText.isEmpty {
|
if !status.spoilerText.asRawText.isEmpty {
|
||||||
EmojiTextApp(status.spoilerText.asMarkdown, emojis: status.emojis)
|
EmojiTextApp(status.spoilerText, emojis: status.emojis)
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
|
@ -189,9 +189,10 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !viewModel.displaySpoiler {
|
if !viewModel.displaySpoiler {
|
||||||
HStack {
|
HStack {
|
||||||
EmojiTextApp(status.content.asMarkdown, emojis: status.emojis)
|
EmojiTextApp(status.content, emojis: status.emojis)
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
routerPath.handleStatus(status: status, url: url)
|
routerPath.handleStatus(status: status, url: url)
|
||||||
|
@ -248,7 +249,7 @@ public struct StatusRowView: View {
|
||||||
AvatarView(url: status.account.avatar, size: .status)
|
AvatarView(url: status.account.avatar, size: .status)
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
EmojiTextApp(status.account.safeDisplayName.asMarkdown, emojis: status.account.emojis)
|
EmojiTextApp(.init(stringValue: status.account.safeDisplayName), emojis: status.account.emojis)
|
||||||
.font(.scaledSubheadline)
|
.font(.scaledSubheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
Group {
|
Group {
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
|
favouritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
|
||||||
reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
|
reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
|
||||||
repliesCount = status.reblog?.repliesCount ?? status.repliesCount
|
repliesCount = status.reblog?.repliesCount ?? status.repliesCount
|
||||||
displaySpoiler = !(status.reblog?.spoilerText ?? status.spoilerText).isEmpty
|
displaySpoiler = !(status.reblog?.spoilerText.asRawText ?? status.spoilerText.asRawText).isEmpty
|
||||||
|
|
||||||
isFiltered = filter != nil
|
isFiltered = filter != nil
|
||||||
}
|
}
|
||||||
|
@ -70,9 +70,8 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
|
|
||||||
func loadEmbeddedStatus() async {
|
func loadEmbeddedStatus() async {
|
||||||
guard let client,
|
guard let client,
|
||||||
let urls = status.content.findStatusesURLs(),
|
!status.content.statusesURLs.isEmpty,
|
||||||
!urls.isEmpty,
|
let url = status.content.statusesURLs.first,
|
||||||
let url = urls.first,
|
|
||||||
client.hasConnection(with: url)
|
client.hasConnection(with: url)
|
||||||
else {
|
else {
|
||||||
isEmbedLoading = false
|
isEmbedLoading = false
|
||||||
|
|
Loading…
Reference in a new issue