Support Custom Emojis (#61)

* Support Custom Emojis

* Update EmojiText

* Update EmojiText

* Use EmojiText in StatusEditorAutoCompleteView

* Update EmojiText

* Display Account displayName without emojis in navigation title

Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
This commit is contained in:
David Walter 2023-01-12 06:58:04 +01:00 committed by GitHub
parent 150cb5a8c5
commit 3acd5aced4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 78 additions and 68 deletions

View file

@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "emojitext",
"kind" : "remoteSourceControl",
"location" : "https://github.com/divadretlaw/EmojiText",
"state" : {
"revision" : "f349e481499d2c832ab9d2dc28af238e53b1f9b4",
"version" : "1.1.0"
}
},
{
"identity" : "html2markdown",
"kind" : "remoteSourceControl",
@ -23,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke",
"state" : {
"revision" : "81f6a3dea0c8ce3b87389c241c48601be07af0b1",
"version" : "11.5.1"
"revision" : "2e9337168d08acccf72c039bf9324be24a1cf7d7",
"version" : "11.5.3"
}
},
{
@ -41,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/scinfu/SwiftSoup.git",
"state" : {
"revision" : "6778575285177365cbad3e5b8a72f2a20583cfec",
"version" : "2.4.3"
"revision" : "f707b8680cddb96dc1855632340a572ef37bbb98",
"version" : "2.5.3"
}
},
{

View file

@ -1,6 +1,7 @@
import SwiftUI
import DesignSystem
import Env
import EmojiText
struct AppAccountView: View {
@EnvironmentObject private var routeurPath: RouterPath
@ -21,7 +22,7 @@ struct AppAccountView: View {
}
VStack(alignment: .leading) {
if let account = viewModel.account {
account.displayNameWithEmojis
EmojiText(account.safeDisplayName, emojis: account.emojis)
Text("\(account.username)@\(viewModel.appAccount.server)")
.font(.subheadline)
.foregroundColor(.gray)

View file

@ -4,6 +4,7 @@ import DesignSystem
import Env
import Shimmer
import NukeUI
import EmojiText
struct AccountDetailHeaderView: View {
@EnvironmentObject private var theme: Theme
@ -108,7 +109,7 @@ struct AccountDetailHeaderView: View {
accountAvatarView
HStack {
VStack(alignment: .leading, spacing: 0) {
account.displayNameWithEmojis
EmojiText(account.safeDisplayName, emojis: account.emojis)
.font(.headline)
Text("@\(account.acct)")
.font(.callout)
@ -120,7 +121,7 @@ struct AccountDetailHeaderView: View {
relationship: relationship))
}
}
Text(account.note.asSafeAttributedString)
EmojiText(account.note, emojis: account.emojis)
.font(.body)
.padding(.top, 8)
.environment(\.openURL, OpenURLAction { url in

View file

@ -5,6 +5,7 @@ import Status
import Shimmer
import DesignSystem
import Env
import EmojiText
public struct AccountDetailView: View {
@Environment(\.redactionReasons) private var reasons
@ -217,7 +218,7 @@ public struct AccountDetailView: View {
Image(systemName: "checkmark.seal")
.foregroundColor(Color.green.opacity(0.80))
}
Text(field.value.asSafeAttributedString)
EmojiText(field.value, emojis: viewModel.account?.emojis ?? [])
.foregroundColor(theme.tintColor)
}
.font(.body)
@ -335,7 +336,8 @@ public struct AccountDetailView: View {
if scrollOffset < -200 {
switch viewModel.accountState {
case let .data(account):
account.displayNameWithEmojis.font(.headline)
EmojiText(account.safeDisplayName, emojis: account.emojis)
.font(.headline)
default:
EmptyView()
}

View file

@ -3,6 +3,7 @@ import Models
import Network
import DesignSystem
import Env
import EmojiText
@MainActor
public class AccountsListRowViewModel: ObservableObject {
@ -32,13 +33,13 @@ public struct AccountsListRow: View {
HStack(alignment: .top) {
AvatarView(url: viewModel.account.avatar, size: .status)
VStack(alignment: .leading, spacing: 2) {
viewModel.account.displayNameWithEmojis
EmojiText(viewModel.account.safeDisplayName, emojis: viewModel.account.emojis)
.font(.subheadline)
.fontWeight(.semibold)
Text("@\(viewModel.account.acct)")
.font(.footnote)
.foregroundColor(.gray)
Text(viewModel.account.note.asSafeAttributedString)
EmojiText(viewModel.account.note, emojis: viewModel.account.emojis)
.font(.footnote)
.lineLimit(3)
.environment(\.openURL, OpenURLAction { url in

View file

@ -17,7 +17,9 @@ let package = Package(
.package(name: "Models", path: "../Models"),
.package(name: "Env", path: "../Env"),
.package(url: "https://github.com/markiv/SwiftUI-Shimmer", exact: "1.1.0"),
.package(url: "https://github.com/kean/Nuke", from: "11.5.0")],
.package(url: "https://github.com/kean/Nuke", from: "11.5.0"),
.package(url: "https://github.com/divadretlaw/EmojiText", from: "1.1.0")
],
targets: [
.target(
name: "DesignSystem",
@ -26,7 +28,8 @@ let package = Package(
.product(name: "Env", package: "Env"),
.product(name: "Shimmer", package: "SwiftUI-Shimmer"),
.product(name: "NukeUI", package: "Nuke"),
.product(name: "Nuke", package: "Nuke")
.product(name: "Nuke", package: "Nuke"),
.product(name: "EmojiText", package: "EmojiText")
]),
]
)

View file

@ -3,7 +3,6 @@ import SwiftUI
import NukeUI
import Models
@MainActor
extension Account {
private struct Part: Identifiable {
let id = UUID().uuidString
@ -17,34 +16,11 @@ extension Account {
return displayName
}
@ViewBuilder
public var displayNameWithEmojis: some View {
if displayName.isEmpty {
Text(safeDisplayName)
}
let splittedDisplayName = displayName.split(separator: ":").map{ Part(value: $0) }
HStack(spacing: 0) {
if displayName.isEmpty {
Text(" ")
}
ForEach(splittedDisplayName, id: \.id) { part in
if let emoji = emojis.first(where: { $0.shortcode == part.value }) {
LazyImage(url: emoji.url) { state in
if let image = state.image {
image
.resizingMode(.aspectFit)
} else if state.isLoading {
ProgressView()
} else {
ProgressView()
}
}
.processors([.resize(size: .init(width: 20, height: 20))])
.frame(width: 20, height: 20)
} else {
Text(part.value)
}
}
public var displayNameWithoutEmojis: String {
var name = safeDisplayName
for emoji in emojis {
name = name.replacingOccurrences(of: ":\(emoji.shortcode):", with: "")
}
return name.split(separator: " ", omittingEmptySubsequences: true).joined(separator: " ")
}
}

View file

@ -0,0 +1,11 @@
import Foundation
import EmojiText
import Models
import HTML2Markdown
public extension EmojiText {
init(_ string: HTMLString, emojis: [Emoji]) {
let markdown = string.asMarkdown
self.init(markdown: markdown, emojis: emojis.map { RemoteEmoji(shortcode: $0.shortcode, url: $0.url) })
}
}

View file

@ -2,6 +2,7 @@ import SwiftUI
import Models
import DesignSystem
import Network
import EmojiText
public struct ListEditView: View {
@Environment(\.dismiss) private var dismiss
@ -30,7 +31,7 @@ public struct ListEditView: View {
HStack {
AvatarView(url: account.avatar, size: .status)
VStack(alignment: .leading) {
account.displayNameWithEmojis
EmojiText(account.safeDisplayName, emojis: account.emojis)
Text("@\(account.acct)")
.foregroundColor(.gray)
.font(.footnote)

View file

@ -10,6 +10,8 @@ extension HTMLString {
do {
let dom = try HTMLParser().parse(html: self)
return dom.toMarkdown()
// Add space between hashtags and mentions that follow each other
.replacingOccurrences(of: ")[", with: ") [")
} catch {
return self
}
@ -44,9 +46,7 @@ extension HTMLString {
public var asSafeAttributedString: AttributedString {
do {
// Add space between hashtags and mentions that follow each other
let markdown = asMarkdown
.replacingOccurrences(of: ")[", with: ") [")
let options = AttributedString.MarkdownParsingOptions(allowsExtendedAttributes: true,
interpretedSyntax: .inlineOnlyPreservingWhitespace)
return try AttributedString(markdown: markdown, options: options)

View file

@ -3,6 +3,7 @@ import Models
import DesignSystem
import Status
import Env
import EmojiText
struct NotificationRowView: View {
@EnvironmentObject private var theme: Theme
@ -50,18 +51,18 @@ struct NotificationRowView: View {
private func makeMainLabel(type: Models.Notification.NotificationType) -> some View {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) {
Text(notification.account.safeDisplayName)
.font(.subheadline)
.fontWeight(.semibold) +
Text(" ") +
Text(type.label())
.font(.subheadline) +
Text("")
.font(.footnote)
.foregroundColor(.gray) +
Text(notification.createdAt.formatted)
.font(.footnote)
.foregroundColor(.gray)
EmojiText(notification.account.safeDisplayName, emojis: notification.account.emojis)
.append {
Text(" ") +
Text(type.label())
.font(.subheadline) +
Text("")
.font(.footnote)
.foregroundColor(.gray) +
Text(notification.createdAt.formatted)
.font(.footnote)
.foregroundColor(.gray)
}
Spacer()
}
}
@ -86,7 +87,7 @@ struct NotificationRowView: View {
.foregroundColor(.gray)
if type == .follow {
Text(notification.account.note.asSafeAttributedString)
EmojiText(notification.account.note, emojis: notification.account.emojis)
.lineLimit(3)
.font(.callout)
.foregroundColor(.gray)

View file

@ -62,7 +62,7 @@ class StatusDetailViewModel: ObservableObject {
let status: Status = try await client.get(endpoint: Statuses.status(id: statusId))
let context: StatusContext = try await client.get(endpoint: Statuses.context(id: statusId))
state = .display(status: status, context: context)
title = "Post from \(status.account.displayName)"
title = "Post from \(status.account.displayNameWithoutEmojis)"
} catch {
state = .error(error: error)
}

View file

@ -1,6 +1,7 @@
import Foundation
import SwiftUI
import DesignSystem
import EmojiText
struct StatusEditorAutoCompleteView: View {
@EnvironmentObject private var theme: Theme
@ -31,7 +32,7 @@ struct StatusEditorAutoCompleteView: View {
HStack {
AvatarView(url: account.avatar, size: .badge)
VStack(alignment: .leading) {
Text(account.displayName)
EmojiText(account.safeDisplayName, emojis: account.emojis)
.font(.footnote)
.foregroundColor(theme.labelColor)
Text("@\(account.acct)")

View file

@ -7,6 +7,7 @@ import Models
import Network
import PhotosUI
import NukeUI
import EmojiText
public struct StatusEditorView: View {
@EnvironmentObject private var preferences: UserPreferences

View file

@ -33,9 +33,9 @@ extension StatusEditorViewModel {
case .edit:
return "Editing your post"
case let .replyTo(status):
return "Replying to \(status.reblog?.account.displayName ?? status.account.displayName)"
return "Replying to \(status.reblog?.account.displayNameWithoutEmojis ?? status.account.displayNameWithoutEmojis)"
case let .quote(status):
return "Quote of \(status.reblog?.account.displayName ?? status.account.displayName)"
return "Quote of \(status.reblog?.account.displayNameWithoutEmojis ?? status.account.displayNameWithoutEmojis)"
}
}
}

View file

@ -1,6 +1,7 @@
import SwiftUI
import Models
import DesignSystem
import EmojiText
@MainActor
public struct StatusEmbededView: View {
@ -34,7 +35,7 @@ public struct StatusEmbededView: View {
HStack(alignment: .center) {
AvatarView(url: account.avatar, size: .embed)
VStack(alignment: .leading, spacing: 0) {
status.account.displayNameWithEmojis
EmojiText(status.account.safeDisplayName, emojis: account.emojis)
.font(.footnote)
.fontWeight(.semibold)
Group {

View file

@ -4,6 +4,7 @@ import Env
import DesignSystem
import Network
import Shimmer
import EmojiText
public struct StatusRowView: View {
@Environment(\.redactionReasons) private var reasons
@ -90,7 +91,7 @@ public struct StatusRowView: View {
HStack(spacing: 2) {
Image(systemName:"arrow.left.arrow.right.circle.fill")
AvatarView(url: viewModel.status.account.avatar, size: .boost)
viewModel.status.account.displayNameWithEmojis
EmojiText(viewModel.status.account.safeDisplayName, emojis: viewModel.status.account.emojis)
Text("boosted")
}
.font(.footnote)
@ -164,7 +165,7 @@ public struct StatusRowView: View {
private func makeStatusContentView(status: AnyStatus) -> some View {
Group {
if !status.spoilerText.isEmpty {
Text(status.spoilerText)
EmojiText(status.spoilerText, emojis: status.emojis)
.font(.body)
Button {
withAnimation {
@ -177,7 +178,7 @@ public struct StatusRowView: View {
}
if !viewModel.displaySpoiler {
HStack {
Text(status.content.asSafeAttributedString)
EmojiText(status.content, emojis: status.emojis)
.font(.body)
.environment(\.openURL, OpenURLAction { url in
routeurPath.handleStatus(status: status, url: url)
@ -233,7 +234,7 @@ public struct StatusRowView: View {
AvatarView(url: status.account.avatar, size: .status)
}
VStack(alignment: .leading, spacing: 0) {
status.account.displayNameWithEmojis
EmojiText(status.account.safeDisplayName, emojis: status.account.emojis)
.font(.headline)
.fontWeight(.semibold)
Group {