commit b771e8f49a21056799b82511ce907b85205c9f9d
parent a88e80a34618ba27c06f21fb3218e137246464fb
Author: Daniel D’Aquino <daniel@daquino.me>
Date: Mon, 24 Jun 2024 11:38:31 -0700
Merge pull request #2295 from tyiu/change-emoji-component
Revamp emoji picker to be less error-prone and add search, frequently used, and multiple skin tone support capabilities
Diffstat:
10 files changed, 107 insertions(+), 104 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -12,6 +12,7 @@
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
+ 3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */; };
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; };
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; };
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; };
@@ -32,7 +33,6 @@
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
- 3AFE89C32BD4156F00AD31EF /* MCEmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */; };
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; };
4C011B5E2BD0A56A002F2F9B /* ChatEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */; };
4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5D2BD0A56A002F2F9B /* ChatroomThreadView.swift */; };
@@ -1497,10 +1497,10 @@
buildActionMask = 2147483647;
files = (
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
+ 3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */,
D78DB8592C1CE9CA00F0AB12 /* SwipeActions in Frameworks */,
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
4C27C9322A64766F007DBC75 /* MarkdownUI in Frameworks */,
- 3AFE89C32BD4156F00AD31EF /* MCEmojiPicker in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2893,7 +2893,7 @@
4C649880286E0EE300EAE2B3 /* secp256k1 */,
4C06670328FC7EC500038D2A /* Kingfisher */,
4C27C9312A64766F007DBC75 /* MarkdownUI */,
- 3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */,
+ 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */,
D78DB8582C1CE9CA00F0AB12 /* SwipeActions */,
);
productName = damus;
@@ -3034,7 +3034,7 @@
4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */,
4C27C9302A64766F007DBC75 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */,
D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
- 3AFE89C12BD4156F00AD31EF /* XCRemoteSwiftPackageReference "MCEmojiPicker" */,
+ 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */,
D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */,
);
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
@@ -4349,12 +4349,12 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
- 3AFE89C12BD4156F00AD31EF /* XCRemoteSwiftPackageReference "MCEmojiPicker" */ = {
+ 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */ = {
isa = XCRemoteSwiftPackageReference;
- repositoryURL = "https://github.com/izyumkin/MCEmojiPicker";
+ repositoryURL = "https://github.com/tyiu/EmojiPicker.git";
requirement = {
kind = upToNextMajorVersion;
- minimumVersion = 1.2.3;
+ minimumVersion = 0.1.1;
};
};
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
@@ -4408,10 +4408,10 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
- 3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */ = {
+ 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */ = {
isa = XCSwiftPackageProductDependency;
- package = 3AFE89C12BD4156F00AD31EF /* XCRemoteSwiftPackageReference "MCEmojiPicker" */;
- productName = MCEmojiPicker;
+ package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */;
+ productName = EmojiPicker;
};
4C06670328FC7EC500038D2A /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
diff --git a/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -2,6 +2,24 @@
"originHash" : "babaf4d5748afecf49bbb702530d8e9576460692f478b0a50ee43195dd4440e2",
"pins" : [
{
+ "identity" : "emojikit",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/tyiu/EmojiKit",
+ "state" : {
+ "revision" : "05805f72d63a6d6a2d7dc7fe14abd37c1317b11a",
+ "version" : "0.1.2"
+ }
+ },
+ {
+ "identity" : "emojipicker",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/tyiu/EmojiPicker.git",
+ "state" : {
+ "revision" : "0c28b4a1a6b8840cf2580bda59517f6d0a733dc8",
+ "version" : "0.1.1"
+ }
+ },
+ {
"identity" : "gsplayer",
"kind" : "remoteSourceControl",
"location" : "https://github.com/wxxsw/GSPlayer",
@@ -20,20 +38,20 @@
}
},
{
- "identity" : "mcemojipicker",
+ "identity" : "secp256k1.swift",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/izyumkin/MCEmojiPicker",
+ "location" : "https://github.com/jb55/secp256k1.swift",
"state" : {
- "revision" : "e0b4903b75ae1cc418d276d84d1cb946b8a1d73c",
- "version" : "1.2.3"
+ "revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
}
},
{
- "identity" : "secp256k1.swift",
+ "identity" : "swift-collections",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/jb55/secp256k1.swift",
+ "location" : "https://github.com/apple/swift-collections.git",
"state" : {
- "revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
+ "revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
+ "version" : "1.1.1"
}
},
{
@@ -63,6 +81,15 @@
}
},
{
+ "identity" : "swift-trie",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/tyiu/swift-trie",
+ "state" : {
+ "revision" : "4c50bff6c168f74425f70476be62a072980d2da7",
+ "version" : "0.1.2"
+ }
+ },
+ {
"identity" : "swipeactions",
"kind" : "remoteSourceControl",
"location" : "https://github.com/aheze/SwipeActions",
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -8,6 +8,7 @@
import SwiftUI
import AVKit
import MediaPlayer
+import EmojiPicker
struct ZapSheet {
let target: ZapTarget
@@ -719,7 +720,8 @@ struct ContentView: View {
music: MusicController(onChange: music_changed),
video: VideoController(),
ndb: ndb,
- quote_reposts: .init(our_pubkey: pubkey)
+ quote_reposts: .init(our_pubkey: pubkey),
+ emoji_provider: DefaultEmojiProvider(showAllVariations: true)
)
home.damus_state = self.damus_state!
diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift
@@ -7,6 +7,7 @@
import Foundation
import LinkPresentation
+import EmojiPicker
class DamusState: HeadlessDamusState {
let pool: RelayPool
@@ -37,8 +38,9 @@ class DamusState: HeadlessDamusState {
let ndb: Ndb
var purple: DamusPurple
var push_notification_client: PushNotificationClient
+ let emoji_provider: EmojiProvider
- init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
+ init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter, emoji_provider: EmojiProvider) {
self.pool = pool
self.keypair = keypair
self.likes = likes
@@ -70,6 +72,7 @@ class DamusState: HeadlessDamusState {
)
self.quote_reposts = quote_reposts
self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings)
+ self.emoji_provider = emoji_provider
}
@discardableResult
@@ -135,7 +138,8 @@ class DamusState: HeadlessDamusState {
music: nil,
video: VideoController(),
ndb: .empty,
- quote_reposts: .init(our_pubkey: empty_pub)
+ quote_reposts: .init(our_pubkey: empty_pub),
+ emoji_provider: DefaultEmojiProvider(showAllVariations: true)
)
}
}
diff --git a/damus/TestData.swift b/damus/TestData.swift
@@ -6,6 +6,7 @@
//
import Foundation
+import EmojiPicker
let test_seckey = Privkey(Data([0xe0, 0xaa, 0x60, 0x26, 0x08, 0x18, 0xac, 0x10, 0x03, 0x86, 0x4d, 0x15, 0x24, 0x9a, 0xf7, 0xa3, 0x3e, 0x4f, 0x1f, 0xc9, 0x01, 0xcf, 0xee, 0xa9, 0xb4, 0x77, 0xc7, 0x07, 0x22, 0xb7, 0x25, 0xfd]))
@@ -100,7 +101,8 @@ var test_damus_state: DamusState = ({
music: .init(onChange: {_ in }),
video: .init(),
ndb: ndb,
- quote_reposts: .init(our_pubkey: our_pubkey)
+ quote_reposts: .init(our_pubkey: our_pubkey),
+ emoji_provider: DefaultEmojiProvider(showAllVariations: true)
)
/*
diff --git a/damus/Util/Router.swift b/damus/Util/Router.swift
@@ -85,7 +85,7 @@ enum Route: Hashable {
case .TranslationSettings(let settings):
TranslationSettingsView(settings: settings, damus_state: damusState)
case .ReactionsSettings(let settings):
- ReactionsSettingsView(settings: settings)
+ ReactionsSettingsView(settings: settings, damus_state: damusState)
case .SearchSettings(let settings):
SearchSettingsView(settings: settings)
case .DeveloperSettings(let settings):
diff --git a/damus/Views/ActionBar/EventActionBar.swift b/damus/Views/ActionBar/EventActionBar.swift
@@ -6,7 +6,8 @@
//
import SwiftUI
-import MCEmojiPicker
+import EmojiPicker
+import EmojiKit
import SwipeActions
struct EventActionBar: View {
@@ -22,7 +23,7 @@ struct EventActionBar: View {
@State var show_share_action: Bool = false
@State var show_repost_action: Bool = false
- @State private var isOnTopHalfOfScreen: Bool = false
+ @State private var selectedEmoji: Emoji? = nil
@ObservedObject var bar: ActionBarModel
@@ -126,7 +127,7 @@ struct EventActionBar: View {
var like_button: some View {
HStack(spacing: 4) {
- LikeButton(damus_state: damus_state, liked: bar.liked, liked_emoji: bar.our_like != nil ? to_reaction_emoji(ev: bar.our_like!) : nil, isOnTopHalfOfScreen: $isOnTopHalfOfScreen) { emoji in
+ LikeButton(damus_state: damus_state, liked: bar.liked, liked_emoji: bar.our_like != nil ? to_reaction_emoji(ev: bar.our_like!) : nil) { emoji in
if bar.liked {
//notify(.delete, bar.our_like)
} else {
@@ -257,20 +258,6 @@ struct EventActionBar: View {
self.bar.our_like = liked.event
}
}
- .background(
- GeometryReader { geometry in
- EmptyView()
- .onAppear {
- let eventActionBarY = geometry.frame(in: .global).midY
- let screenMidY = UIScreen.main.bounds.midY
- self.isOnTopHalfOfScreen = eventActionBarY > screenMidY
- }
- .onChange(of: geometry.frame(in: .global).midY) { newY in
- let screenMidY = UIScreen.main.bounds.midY
- self.isOnTopHalfOfScreen = newY > screenMidY
- }
- }
- )
}
func send_like(emoji: String) {
@@ -315,7 +302,6 @@ struct LikeButton: View {
let damus_state: DamusState
let liked: Bool
let liked_emoji: String?
- @Binding var isOnTopHalfOfScreen: Bool
let action: (_ emoji: String) -> Void
// For reactions background
@@ -324,7 +310,7 @@ struct LikeButton: View {
@State private var isReactionsVisible = false
- @State private var selectedEmoji: String = ""
+ @State private var selectedEmoji: Emoji?
// Following four are Shaka animation properties
let timer = Timer.publish(every: 0.10, on: .main, in: .common).autoconnect()
@@ -363,6 +349,11 @@ struct LikeButton: View {
.foregroundColor(.gray)
}
}
+ .sheet(isPresented: $isReactionsVisible) {
+ NavigationView {
+ EmojiPickerView(selectedEmoji: $selectedEmoji, emojiProvider: damus_state.emoji_provider)
+ }.presentationDetents([.medium, .large])
+ }
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
.onReceive(self.timer) { _ in
@@ -377,14 +368,10 @@ struct LikeButton: View {
amountOfAngleIncrease = 20.0
}
})
- .emojiPicker(
- isPresented: $isReactionsVisible,
- selectedEmoji: $selectedEmoji,
- arrowDirection: isOnTopHalfOfScreen ? .down : .up,
- isDismissAfterChoosing: true
- )
.onChange(of: selectedEmoji) { newSelectedEmoji in
- self.action(newSelectedEmoji)
+ if let newSelectedEmoji {
+ self.action(newSelectedEmoji.value)
+ }
}
}
diff --git a/damus/Views/Chat/ChatEventView.swift b/damus/Views/Chat/ChatEventView.swift
@@ -6,7 +6,8 @@
//
import SwiftUI
-import MCEmojiPicker
+import EmojiKit
+import EmojiPicker
import SwipeActions
fileprivate let CORNER_RADIUS: CGFloat = 10
@@ -34,8 +35,8 @@ struct ChatEventView: View {
generator.impactOccurred()
}
}
- @State var selected_emoji: String = ""
-
+ @State var selected_emoji: Emoji?
+
@State private var isOnTopHalfOfScreen: Bool = false
@ObservedObject var bar: ActionBarModel
@@ -154,19 +155,18 @@ struct ChatEventView: View {
var event_bubble_with_long_press_interaction: some View {
ZStack(alignment: is_ours ? .bottomLeading : .bottomTrailing) {
self.event_bubble
- .emojiPicker(
- isPresented: Binding(get: { popover_state == .open_emoji_selector }, set: { new_state in
- withAnimation(new_state == true ? .easeIn(duration: 0.5) : .easeOut(duration: 0.1)) {
- popover_state = new_state == true ? .open_emoji_selector : .closed
- }
- }),
- selectedEmoji: $selected_emoji,
- arrowDirection: isOnTopHalfOfScreen ? .down : .up,
- isDismissAfterChoosing: false
- )
+ .sheet(isPresented: Binding(get: { popover_state == .open_emoji_selector }, set: { new_state in
+ withAnimation(new_state == true ? .easeIn(duration: 0.5) : .easeOut(duration: 0.1)) {
+ popover_state = new_state == true ? .open_emoji_selector : .closed
+ }
+ })) {
+ NavigationView {
+ EmojiPickerView(selectedEmoji: $selected_emoji, emojiProvider: damus_state.emoji_provider)
+ }.presentationDetents([.medium, .large])
+ }
.onChange(of: selected_emoji) { newSelectedEmoji in
- if newSelectedEmoji != "" {
- send_like(emoji: newSelectedEmoji)
+ if let newSelectedEmoji {
+ send_like(emoji: newSelectedEmoji.value)
popover_state = .closed
}
}
diff --git a/damus/Views/Settings/ReactionsSettingsView.swift b/damus/Views/Settings/ReactionsSettingsView.swift
@@ -6,22 +6,20 @@
//
import SwiftUI
-import MCEmojiPicker
+import EmojiPicker
+import EmojiKit
struct ReactionsSettingsView: View {
@ObservedObject var settings: UserSettingsStore
+ let damus_state: DamusState
@State private var isReactionsVisible: Bool = false
+ @State private var selectedEmoji: Emoji? = nil
+
var body: some View {
Form {
Section {
Text(settings.default_emoji_reaction)
- .emojiPicker(
- isPresented: $isReactionsVisible,
- selectedEmoji: $settings.default_emoji_reaction,
- arrowDirection: .up,
- isDismissAfterChoosing: true
- )
.onTapGesture {
isReactionsVisible = true
}
@@ -31,43 +29,23 @@ struct ReactionsSettingsView: View {
}
.navigationTitle(NSLocalizedString("Reactions", comment: "Title of emoji reactions view"))
.navigationBarTitleDisplayMode(.large)
+ .sheet(isPresented: $isReactionsVisible) {
+ NavigationView {
+ EmojiPickerView(selectedEmoji: $selectedEmoji, emojiProvider: damus_state.emoji_provider)
+ }
+ .presentationDetents([.medium, .large])
+ }
+ .onChange(of: selectedEmoji) { newEmoji in
+ guard let newEmoji else {
+ return
+ }
+ settings.default_emoji_reaction = newEmoji.value
+ }
}
}
-/// From: https://stackoverflow.com/a/39425959
-extension Character {
- /// A simple emoji is one scalar and presented to the user as an Emoji
- var isSimpleEmoji: Bool {
- guard let firstScalar = unicodeScalars.first else { return false }
- return firstScalar.properties.isEmoji && firstScalar.value > 0x238C
- }
-
- /// Checks if the scalars will be merged into an emoji
- var isCombinedIntoEmoji: Bool { unicodeScalars.count > 1 && unicodeScalars.first?.properties.isEmoji ?? false }
-
- var isEmoji: Bool { isSimpleEmoji || isCombinedIntoEmoji }
-}
-
-extension String {
- var isSingleEmoji: Bool { count == 1 && containsEmoji }
-
- var containsEmoji: Bool { contains { $0.isEmoji } }
-
- var containsOnlyEmoji: Bool { !isEmpty && !contains { !$0.isEmoji } }
-
- var emojiString: String { emojis.map { String($0) }.reduce("", +) }
-
- var emojis: [Character] { filter { $0.isEmoji } }
-
- var emojiScalars: [UnicodeScalar] { filter { $0.isEmoji }.flatMap { $0.unicodeScalars } }
-}
-
-func isValidEmoji(_ string: String) -> Bool {
- return string.isSingleEmoji
-}
-
struct ReactionsSettingsView_Previews: PreviewProvider {
static var previews: some View {
- ReactionsSettingsView(settings: UserSettingsStore())
+ ReactionsSettingsView(settings: UserSettingsStore(), damus_state: test_damus_state)
}
}
diff --git a/damusTests/Mocking/MockDamusState.swift b/damusTests/Mocking/MockDamusState.swift
@@ -7,6 +7,7 @@
import Foundation
@testable import damus
+import EmojiPicker
// Generates a test damus state with configurable mock parameters
func generate_test_damus_state(
@@ -50,7 +51,9 @@ func generate_test_damus_state(
music: .init(onChange: {_ in }),
video: .init(),
ndb: ndb,
- quote_reposts: .init(our_pubkey: our_pubkey) )
+ quote_reposts: .init(our_pubkey: our_pubkey),
+ emoji_provider: DefaultEmojiProvider()
+ )
return damus
}