Add multiple reaction support
Changelog-Added: Add support for multiple reactions Closes: https://github.com/damus-io/damus/issues/1335
This commit is contained in:
committed by
William Casarin
parent
815f4d4a96
commit
d11cd76e6a
48
damus/Views/Settings/AddEmojiView.swift
Normal file
48
damus/Views/Settings/AddEmojiView.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// AddEmojiView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Suhail Saqan on 7/16/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AddEmojiView: View {
|
||||
@Binding var emoji: String
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .leading) {
|
||||
HStack{
|
||||
TextField(NSLocalizedString("⚡", comment: "Placeholder example for an emoji reaction"), text: $emoji)
|
||||
.padding(2)
|
||||
.padding(.leading, 25)
|
||||
.opacity(emoji == "" ? 0.5 : 1)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
.onChange(of: emoji) { newEmoji in
|
||||
if let lastEmoji = newEmoji.last.map(String.init), isValidEmoji(lastEmoji) {
|
||||
self.emoji = lastEmoji
|
||||
} else {
|
||||
self.emoji = ""
|
||||
}
|
||||
}
|
||||
|
||||
Label("", image: "close-circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.trailing, -25.0)
|
||||
.opacity((emoji == "") ? 0.0 : 1.0)
|
||||
.onTapGesture {
|
||||
self.emoji = ""
|
||||
}
|
||||
}
|
||||
|
||||
Label("", image: "copy2")
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
if let pastedEmoji = UIPasteboard.general.string {
|
||||
self.emoji = pastedEmoji
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
damus/Views/Settings/EmojiListItemView.swift
Normal file
79
damus/Views/Settings/EmojiListItemView.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// EmojiListItemView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Suhail Saqan on 7/16/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EmojiListItemView: View {
|
||||
@ObservedObject var settings: UserSettingsStore
|
||||
|
||||
let emoji: String
|
||||
let recommended: Bool
|
||||
|
||||
@Binding var showActionButtons: Bool
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
HStack {
|
||||
if showActionButtons {
|
||||
if recommended {
|
||||
AddButton()
|
||||
} else {
|
||||
RemoveButton()
|
||||
}
|
||||
}
|
||||
|
||||
Text(emoji)
|
||||
}
|
||||
}
|
||||
.swipeActions {
|
||||
if !recommended {
|
||||
RemoveButton()
|
||||
.tint(.red)
|
||||
} else {
|
||||
AddButton()
|
||||
.tint(.green)
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
CopyAction(emoji: emoji)
|
||||
}
|
||||
}
|
||||
|
||||
func CopyAction(emoji: String) -> some View {
|
||||
Button {
|
||||
UIPasteboard.general.setValue(emoji, forPasteboardType: "public.plain-text")
|
||||
} label: {
|
||||
Label(NSLocalizedString("Copy", comment: "Button to copy an emoji reaction"), image: "copy2")
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveButton() -> some View {
|
||||
Button(action: {
|
||||
if let index = settings.emoji_reactions.firstIndex(of: emoji) {
|
||||
settings.emoji_reactions.remove(at: index)
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "minus.circle")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundColor(.red)
|
||||
.padding(.leading, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func AddButton() -> some View {
|
||||
Button(action: {
|
||||
settings.emoji_reactions.append(emoji)
|
||||
}) {
|
||||
Image(systemName: "plus.circle")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundColor(.green)
|
||||
.padding(.leading, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
151
damus/Views/Settings/ReactionsSettingsView.swift
Normal file
151
damus/Views/Settings/ReactionsSettingsView.swift
Normal file
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// ReactionsSettingsView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Suhail Saqan on 7/3/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
let default_emoji_reactions = ["🤣", "🤙", "⚡", "💜", "🔥", "😀", "😃", "😄", "🥶"]
|
||||
|
||||
struct ReactionsSettingsView: View {
|
||||
@ObservedObject var settings: UserSettingsStore
|
||||
|
||||
@State var new_emoji: String = ""
|
||||
@State private var showActionButtons = false
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var recommended: [String] {
|
||||
return getMissingRecommendedEmojis(added: settings.emoji_reactions)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
AddEmojiView(emoji: $new_emoji)
|
||||
} header: {
|
||||
Text(NSLocalizedString("Add Emoji", comment: "Label for section for adding an emoji to the reactions list."))
|
||||
.font(.system(size: 18, weight: .heavy))
|
||||
.padding(.bottom, 5)
|
||||
} footer: {
|
||||
HStack {
|
||||
Spacer()
|
||||
if !new_emoji.isEmpty {
|
||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted emoji.")) {
|
||||
new_emoji = ""
|
||||
}
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.frame(width: 80, height: 30)
|
||||
.foregroundColor(.white)
|
||||
.background(LINEAR_GRADIENT)
|
||||
.clipShape(Capsule())
|
||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted emoji.")) {
|
||||
if isValidEmoji(new_emoji) {
|
||||
settings.emoji_reactions.append(new_emoji)
|
||||
new_emoji = ""
|
||||
}
|
||||
}
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.frame(width: 80, height: 30)
|
||||
.foregroundColor(.white)
|
||||
.background(LINEAR_GRADIENT)
|
||||
.clipShape(Capsule())
|
||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Picker(NSLocalizedString("Select default emoji", comment: "Prompt selection of user's default emoji reaction"),
|
||||
selection: $settings.default_emoji_reaction) {
|
||||
ForEach(settings.emoji_reactions, id: \.self) { emoji in
|
||||
Text(emoji)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
List(settings.emoji_reactions, id: \.self) { emoji in
|
||||
EmojiListItemView(settings: settings, emoji: emoji, recommended: false, showActionButtons: $showActionButtons)
|
||||
}
|
||||
} header: {
|
||||
Text("Emoji Reactions", comment: "Section title for emoji reactions that are currently added.")
|
||||
.font(.system(size: 18, weight: .heavy))
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
|
||||
if recommended.count > 0 {
|
||||
Section {
|
||||
List(Array(recommended), id: \.self) { emoji in
|
||||
EmojiListItemView(settings: settings, emoji: emoji, recommended: true, showActionButtons: $showActionButtons)
|
||||
}
|
||||
} header: {
|
||||
Text("Recommended Emojis", comment: "Section title for recommend emojis")
|
||||
.font(.system(size: 18, weight: .heavy))
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Reactions", comment: "Title of emoji reactions view"))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.toolbar {
|
||||
if showActionButtons {
|
||||
Button("Done") {
|
||||
showActionButtons.toggle()
|
||||
}
|
||||
} else {
|
||||
Button("Edit") {
|
||||
showActionButtons.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the emojis that are in the recommended list but the user has not added yet
|
||||
func getMissingRecommendedEmojis(added: [String], recommended: [String] = default_emoji_reactions) -> [String] {
|
||||
let addedSet = Set(added)
|
||||
let missingEmojis = recommended.filter { !addedSet.contains($0) }
|
||||
return missingEmojis
|
||||
}
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user