mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-29 03:21:02 +00:00
Display custom emoji
This commit is contained in:
parent
97de884213
commit
998fa8d500
8 changed files with 108 additions and 7 deletions
|
@ -25,6 +25,9 @@
|
||||||
D0159F9324DE743700E78478 /* SecondaryNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159F8E24DE743700E78478 /* SecondaryNavigationView.swift */; };
|
D0159F9324DE743700E78478 /* SecondaryNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159F8E24DE743700E78478 /* SecondaryNavigationView.swift */; };
|
||||||
D0159F9B24DE748900E78478 /* SidebarNavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159F9524DE743E00E78478 /* SidebarNavigationViewModel.swift */; };
|
D0159F9B24DE748900E78478 /* SidebarNavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159F9524DE743E00E78478 /* SidebarNavigationViewModel.swift */; };
|
||||||
D0159F9C24DE748C00E78478 /* SidebarNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159F9824DE744500E78478 /* SidebarNavigationView.swift */; };
|
D0159F9C24DE748C00E78478 /* SidebarNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159F9824DE744500E78478 /* SidebarNavigationView.swift */; };
|
||||||
|
D0159FA324DE955900E78478 /* CustomEmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159FA224DE955900E78478 /* CustomEmojiText.swift */; };
|
||||||
|
D0159FA524DE989700E78478 /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159FA424DE989700E78478 /* NSMutableAttributedString+Extensions.swift */; };
|
||||||
|
D0159FA624DE98F600E78478 /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0159FA424DE989700E78478 /* NSMutableAttributedString+Extensions.swift */; };
|
||||||
D047FAAE24C3E21200AF17C5 /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D047FA8524C3E21000AF17C5 /* MetatextApp.swift */; };
|
D047FAAE24C3E21200AF17C5 /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D047FA8524C3E21000AF17C5 /* MetatextApp.swift */; };
|
||||||
D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D047FA8524C3E21000AF17C5 /* MetatextApp.swift */; };
|
D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D047FA8524C3E21000AF17C5 /* MetatextApp.swift */; };
|
||||||
D047FAB224C3E21200AF17C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D047FA8724C3E21200AF17C5 /* Assets.xcassets */; };
|
D047FAB224C3E21200AF17C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D047FA8724C3E21200AF17C5 /* Assets.xcassets */; };
|
||||||
|
@ -180,6 +183,8 @@
|
||||||
D0159F8E24DE743700E78478 /* SecondaryNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryNavigationView.swift; sourceTree = "<group>"; };
|
D0159F8E24DE743700E78478 /* SecondaryNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryNavigationView.swift; sourceTree = "<group>"; };
|
||||||
D0159F9524DE743E00E78478 /* SidebarNavigationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarNavigationViewModel.swift; sourceTree = "<group>"; };
|
D0159F9524DE743E00E78478 /* SidebarNavigationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarNavigationViewModel.swift; sourceTree = "<group>"; };
|
||||||
D0159F9824DE744500E78478 /* SidebarNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarNavigationView.swift; sourceTree = "<group>"; };
|
D0159F9824DE744500E78478 /* SidebarNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarNavigationView.swift; sourceTree = "<group>"; };
|
||||||
|
D0159FA224DE955900E78478 /* CustomEmojiText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiText.swift; sourceTree = "<group>"; };
|
||||||
|
D0159FA424DE989700E78478 /* NSMutableAttributedString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D047FA8524C3E21000AF17C5 /* MetatextApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
|
D047FA8524C3E21000AF17C5 /* MetatextApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
|
||||||
D047FA8724C3E21200AF17C5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
D047FA8724C3E21200AF17C5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -286,6 +291,7 @@
|
||||||
D0159F7F24DE739000E78478 /* Views */ = {
|
D0159F7F24DE739000E78478 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D0159FA224DE955900E78478 /* CustomEmojiText.swift */,
|
||||||
D0159F8C24DE743700E78478 /* IdentitiesView.swift */,
|
D0159F8C24DE743700E78478 /* IdentitiesView.swift */,
|
||||||
D0159F8E24DE743700E78478 /* SecondaryNavigationView.swift */,
|
D0159F8E24DE743700E78478 /* SecondaryNavigationView.swift */,
|
||||||
D0159F8D24DE743700E78478 /* TabNavigationView.swift */,
|
D0159F8D24DE743700E78478 /* TabNavigationView.swift */,
|
||||||
|
@ -470,6 +476,7 @@
|
||||||
children = (
|
children = (
|
||||||
D0A1CA7324DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift */,
|
D0A1CA7324DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift */,
|
||||||
D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */,
|
D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */,
|
||||||
|
D0159FA424DE989700E78478 /* NSMutableAttributedString+Extensions.swift */,
|
||||||
D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */,
|
D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */,
|
||||||
D081A40424D0F1A8001B016E /* String+Extensions.swift */,
|
D081A40424D0F1A8001B016E /* String+Extensions.swift */,
|
||||||
D065F53A24D3B33A00741304 /* View+Extensions.swift */,
|
D065F53A24D3B33A00741304 /* View+Extensions.swift */,
|
||||||
|
@ -772,6 +779,7 @@
|
||||||
D0DB6EF424C5228A00D965FE /* AddIdentityView.swift in Sources */,
|
D0DB6EF424C5228A00D965FE /* AddIdentityView.swift in Sources */,
|
||||||
D0DC177424D0B58800A75C65 /* Keychain.swift in Sources */,
|
D0DC177424D0B58800A75C65 /* Keychain.swift in Sources */,
|
||||||
D074577724D29006004758DB /* StubbingWebAuthSession.swift in Sources */,
|
D074577724D29006004758DB /* StubbingWebAuthSession.swift in Sources */,
|
||||||
|
D0159FA524DE989700E78478 /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0ED1BCE24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
D0ED1BCE24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
||||||
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
||||||
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
||||||
|
@ -779,6 +787,7 @@
|
||||||
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||||
D0159F9124DE743700E78478 /* TabNavigationView.swift in Sources */,
|
D0159F9124DE743700E78478 /* TabNavigationView.swift in Sources */,
|
||||||
D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */,
|
D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */,
|
||||||
|
D0159FA324DE955900E78478 /* CustomEmojiText.swift in Sources */,
|
||||||
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
|
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
|
||||||
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
|
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
|
||||||
D0DC175B24D0154F00A75C65 /* MastodonAPI.swift in Sources */,
|
D0DC175B24D0154F00A75C65 /* MastodonAPI.swift in Sources */,
|
||||||
|
@ -802,6 +811,7 @@
|
||||||
D0ED1BD824CF94B200B4899C /* Application.swift in Sources */,
|
D0ED1BD824CF94B200B4899C /* Application.swift in Sources */,
|
||||||
D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */,
|
D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */,
|
||||||
D0BEC94824CA22C400E864C4 /* TimelineViewModel.swift in Sources */,
|
D0BEC94824CA22C400E864C4 /* TimelineViewModel.swift in Sources */,
|
||||||
|
D0159FA624DE98F600E78478 /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0666A4F24C6C39600F3F04B /* Instance.swift in Sources */,
|
D0666A4F24C6C39600F3F04B /* Instance.swift in Sources */,
|
||||||
D0ED1BDB24CF963E00B4899C /* AppAuthorizationEndpoint.swift in Sources */,
|
D0ED1BDB24CF963E00B4899C /* AppAuthorizationEndpoint.swift in Sources */,
|
||||||
D0091B7524DDF4860040E8D2 /* IdentityRepository.swift in Sources */,
|
D0091B7524DDF4860040E8D2 /* IdentityRepository.swift in Sources */,
|
||||||
|
|
37
Shared/Extensions/NSMutableAttributedString+Extensions.swift
Normal file
37
Shared/Extensions/NSMutableAttributedString+Extensions.swift
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
#elseif canImport(AppKit)
|
||||||
|
import AppKit
|
||||||
|
#endif
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
extension NSMutableAttributedString {
|
||||||
|
func insert(emojis: [Emoji], onImageLoad: (() -> Void)?) {
|
||||||
|
for emoji in emojis {
|
||||||
|
let token = ":\(emoji.shortcode):"
|
||||||
|
|
||||||
|
while let tokenRange = string.range(of: token) {
|
||||||
|
let attachment = NSTextAttachment()
|
||||||
|
let attachmentAttributedString = NSAttributedString(attachment: attachment)
|
||||||
|
|
||||||
|
replaceCharacters(in: NSRange(tokenRange, in: string), with: attachmentAttributedString)
|
||||||
|
|
||||||
|
KingfisherManager.shared.retrieveImage(with: emoji.url) { result in
|
||||||
|
guard case let .success(value) = result else { return }
|
||||||
|
attachment.image = value.image
|
||||||
|
onImageLoad?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resizeAttachments(toLineHeight lineHeight: CGFloat) {
|
||||||
|
enumerateAttribute(.attachment, in: NSRange(location: 0, length: length), options: []) { attribute, _, _ in
|
||||||
|
guard let attachment = attribute as? NSTextAttachment else { return }
|
||||||
|
|
||||||
|
attachment.bounds = CGRect(x: 0, y: lineHeight * -0.25, width: lineHeight, height: lineHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
"go" = "Go";
|
"go" = "Go";
|
||||||
"add-identity.instance-url" = "Instance URL";
|
"add-identity.instance-url" = "Instance URL";
|
||||||
"oauth.error.code-not-found" = "OAuth error: code not found";
|
"oauth.error.code-not-found" = "OAuth error: code not found";
|
||||||
"secondary-navigation.accounts" = "Accounts";
|
"secondary-navigation.manage-accounts" = "Manage Accounts";
|
||||||
"secondary-navigation.preferences" = "Preferences";
|
"secondary-navigation.preferences" = "Preferences";
|
||||||
"identities.add" = "Add";
|
"identities.add" = "Add";
|
||||||
"preferences" = "Preferences";
|
"preferences" = "Preferences";
|
||||||
|
|
|
@ -23,11 +23,13 @@ extension Identity {
|
||||||
let id: String
|
let id: String
|
||||||
let identityID: UUID
|
let identityID: UUID
|
||||||
let username: String
|
let username: String
|
||||||
|
let displayName: String
|
||||||
let url: URL
|
let url: URL
|
||||||
let avatar: URL
|
let avatar: URL
|
||||||
let avatarStatic: URL
|
let avatarStatic: URL
|
||||||
let header: URL
|
let header: URL
|
||||||
let headerStatic: URL
|
let headerStatic: URL
|
||||||
|
let emojis: [Emoji]
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Preferences: Codable, Hashable {
|
struct Preferences: Codable, Hashable {
|
||||||
|
|
|
@ -77,11 +77,13 @@ extension IdentityDatabase {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
identityID: identityID,
|
identityID: identityID,
|
||||||
username: account.username,
|
username: account.username,
|
||||||
|
displayName: account.displayName,
|
||||||
url: account.url,
|
url: account.url,
|
||||||
avatar: account.avatar,
|
avatar: account.avatar,
|
||||||
avatarStatic: account.avatarStatic,
|
avatarStatic: account.avatarStatic,
|
||||||
header: account.header,
|
header: account.header,
|
||||||
headerStatic: account.headerStatic)
|
headerStatic: account.headerStatic,
|
||||||
|
emojis: account.emojis)
|
||||||
.save)
|
.save)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
@ -178,11 +180,13 @@ private extension IdentityDatabase {
|
||||||
.indexed()
|
.indexed()
|
||||||
.references("storedIdentity", column: "id", onDelete: .cascade)
|
.references("storedIdentity", column: "id", onDelete: .cascade)
|
||||||
t.column("username", .text).notNull()
|
t.column("username", .text).notNull()
|
||||||
|
t.column("displayName", .text).notNull()
|
||||||
t.column("url", .text).notNull()
|
t.column("url", .text).notNull()
|
||||||
t.column("avatar", .text).notNull()
|
t.column("avatar", .text).notNull()
|
||||||
t.column("avatarStatic", .text).notNull()
|
t.column("avatarStatic", .text).notNull()
|
||||||
t.column("header", .text).notNull()
|
t.column("header", .text).notNull()
|
||||||
t.column("headerStatic", .text).notNull()
|
t.column("headerStatic", .text).notNull()
|
||||||
|
t.column("emojis", .blob).notNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
iOS/Views/CustomEmojiText.swift
Normal file
30
iOS/Views/CustomEmojiText.swift
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CustomEmojiText: UIViewRepresentable {
|
||||||
|
private let attributedText: NSMutableAttributedString
|
||||||
|
private let emoji: [Emoji]
|
||||||
|
private let textStyle: UIFont.TextStyle
|
||||||
|
|
||||||
|
init(text: String, emoji: [Emoji], textStyle: UIFont.TextStyle) {
|
||||||
|
attributedText = NSMutableAttributedString(string: text)
|
||||||
|
self.emoji = emoji
|
||||||
|
self.textStyle = textStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> UILabel {
|
||||||
|
let label = UILabel()
|
||||||
|
|
||||||
|
label.font = UIFont.preferredFont(forTextStyle: textStyle)
|
||||||
|
attributedText.insert(emojis: emoji, onImageLoad: { label.setNeedsDisplay() })
|
||||||
|
attributedText.resizeAttachments(toLineHeight: label.font.lineHeight)
|
||||||
|
label.attributedText = attributedText
|
||||||
|
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UILabel, context: Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,20 @@ struct IdentitiesView: View {
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
KFImage(identity.image,
|
KFImage(identity.image,
|
||||||
options: .downsampled(dimension: 28, scaleFactor: displayScale))
|
options: .downsampled(dimension: 40, scaleFactor: displayScale))
|
||||||
Text(identity.handle)
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
if let account = identity.account {
|
||||||
|
CustomEmojiText(
|
||||||
|
text: account.displayName,
|
||||||
|
emoji: account.emojis,
|
||||||
|
textStyle: .headline)
|
||||||
|
}
|
||||||
|
Text(identity.handle)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
if identity.id == viewModel.identity.id {
|
if identity.id == viewModel.identity.id {
|
||||||
Image(systemName: "checkmark.circle")
|
Image(systemName: "checkmark.circle")
|
||||||
|
|
|
@ -21,14 +21,20 @@ struct SecondaryNavigationView: View {
|
||||||
KFImage(viewModel.identity.image,
|
KFImage(viewModel.identity.image,
|
||||||
options: .downsampled(dimension: 50, scaleFactor: displayScale))
|
options: .downsampled(dimension: 50, scaleFactor: displayScale))
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
if let account = viewModel.identity.account {
|
||||||
|
CustomEmojiText(
|
||||||
|
text: account.displayName,
|
||||||
|
emoji: account.emojis,
|
||||||
|
textStyle: .headline)
|
||||||
|
}
|
||||||
Text(viewModel.identity.handle)
|
Text(viewModel.identity.handle)
|
||||||
.font(.headline)
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.minimumScaleFactor(0.5)
|
.minimumScaleFactor(0.5)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("secondary-navigation.accounts")
|
Text("secondary-navigation.manage-accounts")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue