Add emoji picker

This commit is contained in:
Benoit Martins 2025-07-11 11:16:46 +02:00
parent cfcd55a622
commit c6179c973c
6 changed files with 316 additions and 157 deletions

View file

@ -47,6 +47,8 @@ class ContactAvatarModel: ObservableObject, Identifiable {
private var friendDelegate: FriendDelegate?
init(friend: Friend?, name: String, address: String, withPresence: Bool?) {
self.name = name
self.address = address
self.resetContactAvatarModel(friend: friend, name: name, address: address, withPresence: withPresence)
}

View file

@ -19,6 +19,7 @@
import SwiftUI
import UniformTypeIdentifiers
import ElegantEmojiPicker
// swiftlint:disable line_length
// swiftlint:disable type_body_length
@ -81,6 +82,10 @@ struct ConversationFragment: View {
@State var messageText: String = ""
@State private var chosen: String?
@State private var showPicker = false
@State private var isSheetVisible = false
var body: some View {
NavigationView {
GeometryReader { geometry in
@ -141,6 +146,17 @@ struct ConversationFragment: View {
})
.edgesIgnoringSafeArea(.all)
})
.sheet(isPresented: $showPicker) {
EmojiPickerView(selected: $chosen, isSheetVisible: $isSheetVisible)
.presentationDetents([.medium])
.edgesIgnoringSafeArea(.all)
}
.onChange(of: chosen ?? "") { newValue in
if !newValue.isEmpty {
conversationViewModel.sendReaction(emoji: newValue)
chosen = nil
}
}
.fullScreenCover(isPresented: $isShowCamera) {
ImagePicker(selectedMedia: self.$conversationViewModel.mediasToSend)
.environmentObject(conversationViewModel)
@ -185,6 +201,16 @@ struct ConversationFragment: View {
}
.edgesIgnoringSafeArea(.all)
})
.sheet(isPresented: $showPicker) {
EmojiPickerView(selected: $chosen, isSheetVisible: $isSheetVisible)
.edgesIgnoringSafeArea(.all)
}
.onChange(of: chosen ?? "") { newValue in
if !newValue.isEmpty {
conversationViewModel.sendReaction(emoji: newValue)
chosen = nil
}
}
.fullScreenCover(isPresented: $isShowCamera) {
ImagePicker(selectedMedia: self.$conversationViewModel.mediasToSend)
.environmentObject(conversationViewModel)
@ -892,136 +918,117 @@ struct ConversationFragment: View {
Spacer()
VStack {
HStack {
if conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
if !isSheetVisible {
HStack {
Button {
conversationViewModel.sendReaction(emoji: "👍")
} label: {
Text("👍")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
if conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
conversationViewModel.sendReaction(emoji: "❤️")
} label: {
Text("❤️")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
HStack {
Button {
conversationViewModel.sendReaction(emoji: "👍")
} label: {
Text("👍")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
conversationViewModel.sendReaction(emoji: "❤️")
} label: {
Text("❤️")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "❤️" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
conversationViewModel.sendReaction(emoji: "😂")
} label: {
Text("😂")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😂" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
conversationViewModel.sendReaction(emoji: "😮")
} label: {
Text("😮")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😮" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
conversationViewModel.sendReaction(emoji: "😢")
} label: {
Text("😢")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😢" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
showPicker = true
isSheetVisible = true
} label: {
Image("plus-circle")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: iconSize > 50 ? 55 : iconSize + 5, height: iconSize > 50 ? 55 : iconSize + 5, alignment: .leading)
}
.padding(.top, 3)
.padding(.leading, 2)
.padding(.trailing, 6)
.cornerRadius(10)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "❤️" ? Color.gray200 : .white)
.cornerRadius(10)
.padding(.vertical, 5)
.padding(.horizontal, 10)
.background(.white)
.cornerRadius(20)
Button {
conversationViewModel.sendReaction(emoji: "😂")
} label: {
Text("😂")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
if !conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😂" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
conversationViewModel.sendReaction(emoji: "😮")
} label: {
Text("😮")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😮" ? Color.gray200 : .white)
.cornerRadius(10)
Button {
conversationViewModel.sendReaction(emoji: "😢")
} label: {
Text("😢")
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
}
.padding(.horizontal, 8)
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😢" ? Color.gray200 : .white)
.cornerRadius(10)
/*
Button {
} label: {
Image("plus-circle")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: iconSize > 50 ? 50 : iconSize, height: iconSize > 50 ? 50 : iconSize, alignment: .leading)
}
.padding(.trailing, 5)
*/
}
.padding(.vertical, 5)
.frame(maxWidth: .infinity)
.padding(.horizontal, 10)
.background(.white)
.cornerRadius(20)
if !conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
.padding(.leading, SharedMainViewModel.shared.displayedConversation!.isGroup ? 43 : 0)
.shadow(color: .black.opacity(0.1), radius: 10)
}
.frame(maxWidth: .infinity)
.padding(.horizontal, 10)
.padding(.leading, SharedMainViewModel.shared.displayedConversation!.isGroup ? 43 : 0)
.shadow(color: .black.opacity(0.1), radius: 10)
ChatBubbleView(eventLogMessage: conversationViewModel.selectedMessage!, geometryProxy: geometry)
.environmentObject(conversationViewModel)
.padding(.horizontal, 10)
.padding(.vertical, 1)
.shadow(color: .black.opacity(0.1), radius: 10)
.offset(y: isSheetVisible ? -(UIScreen.main.bounds.height * 0.5) - 10 : 0)
HStack {
if conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
VStack {
Button {
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
conversationViewModel.selectedMessage = nil
conversationViewModel.replyToMessage(index: indexMessage ?? 0)
} label: {
HStack {
Text("menu_reply_to_chat_message")
.default_text_style(styleSize: 15)
Spacer()
Image("reply")
.resizable()
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(.vertical, 5)
.padding(.horizontal, 20)
if !isSheetVisible {
HStack {
if conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
Divider()
if !conversationViewModel.selectedMessage!.message.text.isEmpty {
VStack {
Button {
UIPasteboard.general.setValue(
conversationViewModel.selectedMessage?.message.text ?? "Error_message_not_available",
forPasteboardType: UTType.plainText.identifier
)
ToastViewModel.shared.toastMessage = "Success_message_copied_into_clipboard"
ToastViewModel.shared.displayToast = true
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
conversationViewModel.selectedMessage = nil
conversationViewModel.replyToMessage(index: indexMessage ?? 0)
} label: {
HStack {
Text("menu_copy_chat_message")
Text("menu_reply_to_chat_message")
.default_text_style(styleSize: 15)
Spacer()
Image("copy")
Image("reply")
.resizable()
.frame(width: 20, height: 20, alignment: .leading)
}
@ -1030,59 +1037,86 @@ struct ConversationFragment: View {
}
Divider()
if !conversationViewModel.selectedMessage!.message.text.isEmpty {
Button {
UIPasteboard.general.setValue(
conversationViewModel.selectedMessage?.message.text ?? "Error_message_not_available",
forPasteboardType: UTType.plainText.identifier
)
ToastViewModel.shared.toastMessage = "Success_message_copied_into_clipboard"
ToastViewModel.shared.displayToast = true
conversationViewModel.selectedMessage = nil
} label: {
HStack {
Text("menu_copy_chat_message")
.default_text_style(styleSize: 15)
Spacer()
Image("copy")
.resizable()
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(.vertical, 5)
.padding(.horizontal, 20)
}
Divider()
}
Button {
withAnimation {
isShowConversationForwardMessageFragment = true
}
} label: {
HStack {
Text("menu_forward_chat_message")
.default_text_style(styleSize: 15)
Spacer()
Image("forward")
.resizable()
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(.vertical, 5)
.padding(.horizontal, 20)
}
Divider()
Button {
conversationViewModel.deleteMessage()
} label: {
HStack {
Text("menu_delete_selected_item")
.foregroundStyle(.red)
.default_text_style(styleSize: 15)
Spacer()
Image("trash-simple-red")
.renderingMode(.template)
.resizable()
.foregroundStyle(.red)
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(.vertical, 5)
.padding(.horizontal, 20)
}
}
.frame(maxWidth: geometry.size.width / 1.5)
.padding(.vertical, 8)
.background(.white)
.cornerRadius(20)
Button {
withAnimation {
isShowConversationForwardMessageFragment = true
}
} label: {
HStack {
Text("menu_forward_chat_message")
.default_text_style(styleSize: 15)
Spacer()
Image("forward")
.resizable()
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(.vertical, 5)
.padding(.horizontal, 20)
}
Divider()
Button {
conversationViewModel.deleteMessage()
} label: {
HStack {
Text("menu_delete_selected_item")
.foregroundStyle(.red)
.default_text_style(styleSize: 15)
Spacer()
Image("trash-simple-red")
.renderingMode(.template)
.resizable()
.foregroundStyle(.red)
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(.vertical, 5)
.padding(.horizontal, 20)
if !conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
}
.frame(maxWidth: geometry.size.width / 1.5)
.padding(.vertical, 8)
.background(.white)
.cornerRadius(20)
if !conversationViewModel.selectedMessage!.message.isOutgoing {
Spacer()
}
.frame(maxWidth: .infinity)
.padding(.horizontal, 10)
.padding(.bottom, 20)
.padding(.leading, SharedMainViewModel.shared.displayedConversation!.isGroup ? 43 : 0)
.shadow(color: .black.opacity(0.1), radius: 10)
}
.frame(maxWidth: .infinity)
.padding(.horizontal, 10)
.padding(.bottom, 20)
.padding(.leading, SharedMainViewModel.shared.displayedConversation!.isGroup ? 43 : 0)
.shadow(color: .black.opacity(0.1), radius: 10)
}
}
.frame(maxWidth: .infinity)

View file

@ -2261,8 +2261,15 @@ class ConversationViewModel: ObservableObject {
if chatMessageReaction.fromAddress != nil {
dispatchGroup.enter()
ContactAvatarModel.getAvatarModelFromAddress(address: chatMessageReaction.fromAddress!) { avatarResult in
if core.defaultAccount != nil && core.defaultAccount!.contactAddress != nil && core.defaultAccount!.contactAddress!.asStringUriOnly().contains(avatarResult.address) {
let innerSheetCat = InnerSheetCategory(contact: avatarResult, detail: chatMessageReaction.body, isMe: true)
if let account = core.defaultAccount,
let contactAddress = account.contactAddress,
contactAddress.asStringUriOnly().contains(avatarResult.address) {
let innerSheetCat = InnerSheetCategory(
contact: avatarResult,
detail: chatMessageReaction.body,
isMe: true
)
participantList[0].append(innerSheetCat)
} else {
let innerSheetCat = InnerSheetCategory(contact: avatarResult, detail: chatMessageReaction.body)

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI
import ElegantEmojiPicker
struct EmojiPickerView: UIViewControllerRepresentable {
@Binding var selected: String?
@Binding var isSheetVisible: Bool
var configuration = ElegantConfiguration(showRandom: false, showReset: false)
func makeUIViewController(context: Context) -> UIViewController {
let picker = ElegantEmojiPicker(delegate: context.coordinator,
configuration: configuration,
localization: .init())
let container = NotifyingViewController()
container.onWillDisappear = {
isSheetVisible = false
}
container.addChild(picker)
container.view.addSubview(picker.view)
picker.view.frame = container.view.bounds
picker.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
picker.didMove(toParent: container)
return container
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class NotifyingViewController: UIViewController {
var onWillDisappear: (() -> Void)?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onWillDisappear?()
}
}
final class Coordinator: NSObject, ElegantEmojiPickerDelegate {
let parent: EmojiPickerView
init(_ parent: EmojiPickerView) { self.parent = parent }
func emojiPicker(_ picker: ElegantEmojiPicker, didSelectEmoji emoji: Emoji?) {
parent.selected = emoji?.emoji
picker.dismiss(animated: true)
}
func emojiPicker(_ picker: ElegantEmojiPicker,
loadEmojiSections withConfiguration: ElegantConfiguration,
_ withLocalization: ElegantLocalization) -> [EmojiSection] {
return ElegantEmojiPicker.getDefaultEmojiSections()
}
}
}

View file

@ -182,6 +182,8 @@
D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */; };
D7DC096F2CFA1D7600A6D47C /* AccountProfileFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DC096E2CFA1D7400A6D47C /* AccountProfileFragment.swift */; };
D7DC09712CFDBF9A00A6D47C /* AccountProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DC09702CFDBF8300A6D47C /* AccountProfileViewModel.swift */; };
D7DF8BE72E2104DC003A3BC7 /* ElegantEmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = D7DF8BE62E2104DC003A3BC7 /* ElegantEmojiPicker */; };
D7DF8BE92E2104EC003A3BC7 /* EmojiPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF8BE82E2104E5003A3BC7 /* EmojiPickerView.swift */; };
D7E2E69F2CE356C90080DA0D /* PopupViewWithTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E2E69E2CE356C90080DA0D /* PopupViewWithTextField.swift */; };
D7E394C52DAC6561005FA0DD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D737AEED2DA011F2005C1280 /* Localizable.strings */; };
D7E6ADF32B9875C20009A2BC /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6ADF22B9875C20009A2BC /* Message.swift */; };
@ -400,6 +402,7 @@
D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModeFragment.swift; sourceTree = "<group>"; };
D7DC096E2CFA1D7400A6D47C /* AccountProfileFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProfileFragment.swift; sourceTree = "<group>"; };
D7DC09702CFDBF8300A6D47C /* AccountProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProfileViewModel.swift; sourceTree = "<group>"; };
D7DF8BE82E2104E5003A3BC7 /* EmojiPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerView.swift; sourceTree = "<group>"; };
D7E2E69E2CE356C90080DA0D /* PopupViewWithTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupViewWithTextField.swift; sourceTree = "<group>"; };
D7E6ADF22B9875C20009A2BC /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
D7E6ADF42B9876ED0009A2BC /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
@ -445,6 +448,7 @@
buildActionMask = 2147483647;
files = (
D7D5AD7F2DD34F0E00016721 /* AppAuth in Frameworks */,
D7DF8BE72E2104DC003A3BC7 /* ElegantEmojiPicker in Frameworks */,
C618BF562D75CA03005A00E0 /* linphonesw in Frameworks */,
D7D5AD832DD34F2300016721 /* FirebaseCrashlytics in Frameworks */,
D7D5AD812DD34F1A00016721 /* FirebaseAnalytics in Frameworks */,
@ -575,6 +579,7 @@
D717071C2AC591EF0037746F /* Utils */ = {
isa = PBXGroup;
children = (
D7DF8BE82E2104E5003A3BC7 /* EmojiPickerView.swift */,
D703F7072DC8C5FF005B8F75 /* FilePicker.swift */,
D717A10D2CEB770D00849D92 /* ShareSheetController.swift */,
66C491F72B24D25A00CEA16D /* Extensions */,
@ -602,6 +607,7 @@
D719ABB52ABC67BF00B41C10 /* Linphone */,
660AAF7C2B839272004C0FA6 /* msgNotificationService */,
D7458F302E0BDCF4000C957A /* linphoneExtension */,
D7DF8BE52E2104DC003A3BC7 /* Frameworks */,
D719ABB42ABC67BF00B41C10 /* Products */,
);
sourceTree = "<group>";
@ -1039,6 +1045,13 @@
path = ViewModel;
sourceTree = "<group>";
};
D7DF8BE52E2104DC003A3BC7 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -1140,6 +1153,7 @@
D7D5AD7B2DD34E4D00016721 /* XCRemoteSwiftPackageReference "AppAuth-iOS" */,
D7D5AD7C2DD34E7C00016721 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
D7BEB1152E1FF670004B25A3 /* XCRemoteSwiftPackageReference "linphone-sdk-swift-ios" */,
D7DF8BE42E2104D0003A3BC7 /* XCRemoteSwiftPackageReference "Elegant-Emoji-Picker" */,
);
productRefGroup = D719ABB42ABC67BF00B41C10 /* Products */;
projectDirPath = "";
@ -1244,6 +1258,7 @@
D7B5678E2B28888F00DE63EB /* CallView.swift in Sources */,
D71A0E192B485ADF0002C6CD /* ViewExtension.swift in Sources */,
D759CB642C3FBD4200AC35E8 /* StartConversationFragment.swift in Sources */,
D7DF8BE92E2104EC003A3BC7 /* EmojiPickerView.swift in Sources */,
66FDB7812C7C689A00561566 /* EventEditViewController.swift in Sources */,
D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */,
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */,
@ -1889,6 +1904,14 @@
minimumVersion = 11.12.0;
};
};
D7DF8BE42E2104D0003A3BC7 /* XCRemoteSwiftPackageReference "Elegant-Emoji-Picker" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Finalet/Elegant-Emoji-Picker";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -1927,6 +1950,11 @@
package = D7D5AD7C2DD34E7C00016721 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseCrashlytics;
};
D7DF8BE62E2104DC003A3BC7 /* ElegantEmojiPicker */ = {
isa = XCSwiftPackageProductDependency;
package = D7DF8BE42E2104D0003A3BC7 /* XCRemoteSwiftPackageReference "Elegant-Emoji-Picker" */;
productName = ElegantEmojiPicker;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = D719ABAB2ABC67BF00B41C10 /* Project object */;

View file

@ -1,5 +1,5 @@
{
"originHash" : "5293adb495d47691babe33d739eb8e74cee00c05a442b57ef6079701fee324f1",
"originHash" : "7ed0929d53447e6dba6aae5d5c6617250fb4460459e56cf06feaf0f1397ae64e",
"pins" : [
{
"identity" : "abseil-cpp-binary",
@ -28,6 +28,15 @@
"version" : "2.0.0"
}
},
{
"identity" : "elegant-emoji-picker",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Finalet/Elegant-Emoji-Picker",
"state" : {
"branch" : "main",
"revision" : "12c1a2be1adbe7a774ebdd2c48f02d95b8884df6"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",