Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grid based Emoji Picker #159

Draft
wants to merge 10 commits into
base: stable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
update_emoji:
curl https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json -o Nio/Supporting\ Files/emoji.json

.PHONY: update_emoji
18 changes: 16 additions & 2 deletions Nio.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
3902B8A0239410EE00698B87 /* ContentSizeCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902B89F239410EE00698B87 /* ContentSizeCategory.swift */; };
3902B8A32395935600698B87 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902B8A22395935600698B87 /* SettingsView.swift */; };
3902B8A52395A77800698B87 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902B8A42395A77800698B87 /* LoadingView.swift */; };
3902F93C25ACE00D009F5991 /* EmojiCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F93B25ACE00D009F5991 /* EmojiCollection.swift */; };
3902F94125ACE048009F5991 /* emoji.json in Resources */ = {isa = PBXBuildFile; fileRef = 3902F94025ACE048009F5991 /* emoji.json */; };
3902F94625ACE06B009F5991 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F94525ACE06B009F5991 /* UserDefault.swift */; };
3902F96C25AD01E2009F5991 /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3902F96B25AD01E2009F5991 /* SearchField.swift */; };
390D63A42465F62D00B8F640 /* BlurHash in Frameworks */ = {isa = PBXBuildFile; productRef = 390D63A32465F62D00B8F640 /* BlurHash */; };
390D63BF246F4BEE00B8F640 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 390D63BD246F4BEE00B8F640 /* [email protected] */; };
390D63C0246F4BEE00B8F640 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 390D63BE246F4BEE00B8F640 /* [email protected] */; };
Expand Down Expand Up @@ -206,6 +210,10 @@
3902B8A22395935600698B87 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
3902B8A42395A77800698B87 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
3902D4E5248277310009355A /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
3902F93B25ACE00D009F5991 /* EmojiCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiCollection.swift; sourceTree = "<group>"; };
3902F94025ACE048009F5991 /* emoji.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = emoji.json; sourceTree = "<group>"; };
3902F94525ACE06B009F5991 /* UserDefault.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = "<group>"; };
3902F96B25AD01E2009F5991 /* SearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = "<group>"; };
390D639E246327E900B8F640 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
390D63BD246F4BEE00B8F640 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
390D63BE246F4BEE00B8F640 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -472,6 +480,7 @@
3922219524366989004D8794 /* EventContextMenu.swift */,
3922219F2437285B004D8794 /* ReactionPicker.swift */,
392221AF243A6F8E004D8794 /* EventContextMenuModel.swift */,
3902F93B25ACE00D009F5991 /* EmojiCollection.swift */,
);
path = ContextMenu;
sourceTree = "<group>";
Expand All @@ -484,6 +493,7 @@
CAC46D6E23A56BBE0079C24F /* GroupingIterator.swift */,
CAC46D7223A6AE890079C24F /* TypedEvents.swift */,
3984654423B7ECBA006C173B /* MXURL.swift */,
3902F94525ACE06B009F5991 /* UserDefault.swift */,
);
path = Utility;
sourceTree = "<group>";
Expand All @@ -497,6 +507,7 @@
4B0A2E46245E2EF800A79443 /* MultilineTextField.swift */,
CAAF5BF72478696F006FDC33 /* UITextViewWrapper.swift */,
4B29F5B42466EC240084043B /* ImagePicker.swift */,
3902F96B25AD01E2009F5991 /* SearchField.swift */,
);
path = "Shared Views";
sourceTree = "<group>";
Expand Down Expand Up @@ -548,8 +559,6 @@
children = (
A51BF8CD254C2FE5000FB0A4 /* NioApp.swift */,
4BFEFD90246F5EF500CCF4A0 /* Nio.entitlements */,
39C931DC2384328A004449E1 /* AppDelegate.swift */,
39C931DE2384328A004449E1 /* SceneDelegate.swift */,
39C931E02384328A004449E1 /* RootView.swift */,
3902B8A62395A78100698B87 /* Authentication */,
CAC46D5E23A276B30079C24F /* Shapes */,
Expand Down Expand Up @@ -579,6 +588,7 @@
39C931F3238449C2004449E1 /* Supporting Files */ = {
isa = PBXGroup;
children = (
3902F94025ACE048009F5991 /* emoji.json */,
39C931F623846B2D004449E1 /* .swiftlint.yml */,
3921175B244255D600892B00 /* swiftgen.yml */,
39C931E72384328B004449E1 /* LaunchScreen.storyboard */,
Expand Down Expand Up @@ -994,6 +1004,7 @@
39C931E62384328B004449E1 /* Preview Assets.xcassets in Resources */,
39DD77BA2470C49D00A29DEE /* Six Colors [email protected] in Resources */,
390D63C0246F4BEE00B8F640 /* [email protected] in Resources */,
3902F94125ACE048009F5991 /* emoji.json in Resources */,
390D63BF246F4BEE00B8F640 /* [email protected] in Resources */,
39DD77B52470C38300A29DEE /* Six Colors [email protected] in Resources */,
39C931E32384328B004449E1 /* Assets.xcassets in Resources */,
Expand Down Expand Up @@ -1268,6 +1279,9 @@
39C931F523846966004449E1 /* LoginView.swift in Sources */,
CADF662624614A3300F5063F /* ReactionsListItemView.swift in Sources */,
39BA0727240B534600FD28C6 /* Color+allAccent.swift in Sources */,
3902F96C25AD01E2009F5991 /* SearchField.swift in Sources */,
3902F93C25ACE00D009F5991 /* EmojiCollection.swift in Sources */,
3902F94625ACE06B009F5991 /* UserDefault.swift in Sources */,
CAD6817A246C31EB001878EB /* String+Extensions.swift in Sources */,
CAC46D5D23A276700079C24F /* Color+Named.swift in Sources */,
CAF2AE94245B507400D84133 /* Assets.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions Nio.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
"state": {
"branch": null,
"revision": "de5c32c15ae169cfcb27397ffb2734dcd0e1e6d5",
"version": "0.1.0"
"revision": "36ecf80429d00a4cd1e81fbfe4655b1d99ebd651",
"version": "0.1.2"
}
}
]
Expand Down
106 changes: 106 additions & 0 deletions Nio/Conversations/ContextMenu/EmojiCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import SwiftUI

class EmojiCollection {
enum Category: String, CaseIterable, Decodable, Identifiable {
case smileysAndEmotion = "Smileys & Emotion"
case peopleAndBody = "People & Body"
case animalsAndNature = "Animals & Nature"
case foodAndDrink = "Food & Drink"
case travelAndPlaces = "Travel & Places"
case activities = "Activities"
case objects = "Objects"
case symbols = "Symbols"
case flags = "Flags"

var id: String {
rawValue
}

var name: String {
// FIXME: This should be localized.
rawValue
}

var icon: String {
switch self {
case .smileysAndEmotion:
return "😃"
case .peopleAndBody:
return "👋"
case .animalsAndNature:
return "🐰"
case .foodAndDrink:
return "🍔"
case .travelAndPlaces:
return "🌇"
case .activities:
return "⚽️"
case .objects:
return "💡"
case .symbols:
return "🔣"
case .flags:
return "🏳️‍🌈"
}
}

var iconImage: Image {
switch self {
case .smileysAndEmotion:
return Image(systemName: "face.smiling")
case .peopleAndBody:
return Image(systemName: "hand.raised")
case .animalsAndNature:
return Image(systemName: "hare")
case .foodAndDrink:
return Image(systemName: "leaf")
case .travelAndPlaces:
return Image(systemName: "car")
case .activities:
return Image(systemName: "die.face.5")
case .objects:
return Image(systemName: "lightbulb")
case .symbols:
return Image(systemName: "number.circle")
case .flags:
return Image(systemName: "flag")
}
}
}

struct Emoji: Decodable, Identifiable, Hashable {
let emoji: String
let description: String
let category: Category
let aliases: [String]
let tags: [String]

var id: String {
emoji
}
}

let emoji: [Emoji]
let categorized: [Category: [Emoji]]

init() {
let emojiURL = Bundle.main.url(forResource: "emoji", withExtension: "json")!
// swiftlint:disable force_try
let emojiData = try! Data(contentsOf: emojiURL)
emoji = try! JSONDecoder().decode([Emoji].self, from: emojiData)
// swiftlint:enable force_try
categorized = Dictionary(grouping: emoji, by: \.category)
}

func emoji(for category: Category) -> [Emoji] {
categorized[category] ?? []
}

func emoji(matching query: String) -> [Emoji] {
// swiftlint:disable:next identifier_name
emoji.filter { e in
let matchString = e.emoji + e.description + e.aliases.joined() + e.tags.joined()
return matchString.lowercased().contains(query.lowercased())
}
}
}
96 changes: 80 additions & 16 deletions Nio/Conversations/ContextMenu/ReactionPicker.swift
Original file line number Diff line number Diff line change
@@ -1,31 +1,95 @@
import SwiftUI

struct ReactionPicker: View {
let emoji = ["👍", "👎", "😄", "🎉", "❤️", "🚀", "👀"]
public struct ReactionPicker: View {
@Environment(\.colorScheme) var colorScheme

var picked: (String) -> Void
@State private var searchQuery = ""
@State private var selectedCategory: EmojiCollection.Category = .smileysAndEmotion
private var emoji = EmojiCollection()

var body: some View {
VStack {
Text(L10n.ReactionPicker.title)
.foregroundColor(.gray)
func headerView(for category: EmojiCollection.Category) -> some View {
HStack {
Text(category.name)
.font(.headline)
.padding(.bottom, 30)
HStack(spacing: 10) {
ForEach(emoji, id: \.self) { emoji in
Button(action: { self.picked(emoji) },
label: {
Text(emoji)
.font(.largeTitle)
})
.padding(.vertical, 5)
Spacer()
}
}

var onSelect: (String) -> Void

public init(onSelect: @escaping (String) -> Void) {
self.onSelect = onSelect
}

let columns = [
GridItem(.adaptive(minimum: 40), spacing: 20)
]

public var body: some View {
VStack {
// Grab-Handle Thingy
Color.gray
.frame(width: 40, height: 6)
.cornerRadius(3.0)
.opacity(0.8)
.padding(.top)

SearchField(placeholder: L10n.ReactionPicker.search, query: $searchQuery)

if searchQuery != "" {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(emoji.emoji(matching: searchQuery)) { emoji in
EmojiButtonView(emoji: emoji) { emoji in
onSelect(emoji)
}
}
}
}
.padding(.horizontal)
} else {
Picker("", selection: $selectedCategory) {
ForEach(EmojiCollection.Category.allCases) { category in
category.iconImage
.tag(category)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal)

ScrollView {
LazyVGrid(columns: columns) {
// Section(header: headerView(for: selectedCategory)) {
ForEach(emoji.emoji(for: selectedCategory), id: \.self) { emoji in
EmojiButtonView(emoji: emoji) { emoji in
onSelect(emoji)
}
kiliankoe marked this conversation as resolved.
Show resolved Hide resolved
}
// }
}
.padding(.horizontal)
}
}
}
}
}

struct EmojiButtonView: View {
let emoji: EmojiCollection.Emoji
let action: (String) -> Void

var body: some View {
Text(emoji.emoji)
.font(.system(size: 40))
.onTapGesture {
action(emoji.emoji)
}
}
}

struct ReactionPicker_Previews: PreviewProvider {
static var previews: some View {
ReactionPicker(picked: { _ in })
ReactionPicker { _ in }
}
}
6 changes: 4 additions & 2 deletions Nio/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,10 @@ internal enum L10n {
}

internal enum ReactionPicker {
/// Tap on an emoji to send that reaction.
internal static let title = L10n.tr("Localizable", "reaction-picker.title")
/// Reactions
internal static let navigationTitle = L10n.tr("Localizable", "reaction-picker.navigation-title")
/// Search Emoji...
internal static let search = L10n.tr("Localizable", "reaction-picker.search")
}

internal enum RecentRooms {
Expand Down
48 changes: 48 additions & 0 deletions Nio/Shared Views/SearchField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import SwiftUI

struct SearchField: View {
@Environment(\.colorScheme) var colorScheme
let placeholder: String
@Binding var query: String

var body: some View {
ZStack(alignment: .trailing) {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField(self.placeholder, text: $query)
}
.padding(8)
.background(Color(colorScheme == .light ? #colorLiteral(red: 0.9332516193, green: 0.9333857894, blue: 0.941064477, alpha: 1) : #colorLiteral(red: 0.1882131398, green: 0.1960922778, blue: 0.2195765972, alpha: 1)).cornerRadius(8))
.padding(.horizontal)

if query != "" {
Button {
query = ""
} label: {
Image(systemName: "xmark.circle.fill")
}
.foregroundColor(.gray)
// Adding standard padding with the extra padding we have on the text field above.
.padding(.trailing, 8)
.padding(.trailing)
}
}
}
}

struct SearchField_Previews: PreviewProvider {
static var previews: some View {
Group {
enumeratingColorSchemes {
VStack {
SearchField(placeholder: "Search...", query: .constant(""))
.padding()
SearchField(placeholder: "", query: .constant("foobar"))
.padding([.bottom, .horizontal])
}
}
}
.previewLayout(.sizeThatFits)
}
}
3 changes: 2 additions & 1 deletion Nio/Supporting Files/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
"event.context-menu.reply" = "Direkt antworten";
"event.context-menu.edit" = "Bearbeiten";
"event.context-menu.remove" = "Entfernen";
"reaction-picker.title" = "Tippe auf ein Emoji um es zu verschicken.";
"reaction-picker.navigation-title" = "Reaktionen";
"reaction-picker.search" = "Emoji durchsuchen...";
"settings.title" = "Einstellungen";
"settings.accent-color" = "Akzentfarbe";
"settings.app-icon" = "App-Icon";
Expand Down
Loading