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
@@ -45,6 +45,7 @@
|
|||||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
|
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
|
||||||
4C0C03992A61E27B0098B3B8 /* primal.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03972A61E27B0098B3B8 /* primal.wasm */; };
|
4C0C03992A61E27B0098B3B8 /* primal.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03972A61E27B0098B3B8 /* primal.wasm */; };
|
||||||
4C0C039A2A61E27B0098B3B8 /* bool_setting.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */; };
|
4C0C039A2A61E27B0098B3B8 /* bool_setting.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */; };
|
||||||
|
4C15C7152A55DE7A00D0A0DB /* ReactionsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */; };
|
||||||
4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; };
|
4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; };
|
||||||
4C190F252A547D2000027FD5 /* LoadScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F242A547D2000027FD5 /* LoadScript.swift */; };
|
4C190F252A547D2000027FD5 /* LoadScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F242A547D2000027FD5 /* LoadScript.swift */; };
|
||||||
4C198DEF29F88C6B004C165C /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */; };
|
4C198DEF29F88C6B004C165C /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */; };
|
||||||
@@ -339,6 +340,8 @@
|
|||||||
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
||||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
|
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
|
||||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
|
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
|
||||||
|
BA4AB0AE2A63B9270070A32A /* AddEmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */; };
|
||||||
|
BA4AB0B02A63B94D0070A32A /* EmojiListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */; };
|
||||||
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||||
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
||||||
@@ -497,6 +500,7 @@
|
|||||||
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
|
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
|
||||||
4C0C03972A61E27B0098B3B8 /* primal.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = primal.wasm; path = nostrscript/primal.wasm; sourceTree = SOURCE_ROOT; };
|
4C0C03972A61E27B0098B3B8 /* primal.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = primal.wasm; path = nostrscript/primal.wasm; sourceTree = SOURCE_ROOT; };
|
||||||
4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = bool_setting.wasm; path = nostrscript/bool_setting.wasm; sourceTree = SOURCE_ROOT; };
|
4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = bool_setting.wasm; path = nostrscript/bool_setting.wasm; sourceTree = SOURCE_ROOT; };
|
||||||
|
4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsSettingsView.swift; sourceTree = "<group>"; };
|
||||||
4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
|
4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
|
||||||
4C190F242A547D2000027FD5 /* LoadScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadScript.swift; sourceTree = "<group>"; };
|
4C190F242A547D2000027FD5 /* LoadScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadScript.swift; sourceTree = "<group>"; };
|
||||||
4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = "<group>"; };
|
4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = "<group>"; };
|
||||||
@@ -843,6 +847,8 @@
|
|||||||
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
|
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
|
||||||
9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; };
|
9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; };
|
||||||
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
|
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
|
||||||
|
BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEmojiView.swift; sourceTree = "<group>"; };
|
||||||
|
BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiListItemView.swift; sourceTree = "<group>"; };
|
||||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
||||||
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
||||||
@@ -1085,6 +1091,7 @@
|
|||||||
4C1A9A1B29DDCF8B00516EAC /* Settings */ = {
|
4C1A9A1B29DDCF8B00516EAC /* Settings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */,
|
||||||
4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */,
|
4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */,
|
||||||
4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */,
|
4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */,
|
||||||
4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */,
|
4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */,
|
||||||
@@ -1092,6 +1099,8 @@
|
|||||||
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
|
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
|
||||||
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */,
|
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */,
|
||||||
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */,
|
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */,
|
||||||
|
BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */,
|
||||||
|
BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */,
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1939,6 +1948,7 @@
|
|||||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
||||||
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */,
|
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */,
|
||||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||||
|
4C15C7152A55DE7A00D0A0DB /* ReactionsSettingsView.swift in Sources */,
|
||||||
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
|
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
|
||||||
4C7D09742A0AEF9000943473 /* AlbyGradient.swift in Sources */,
|
4C7D09742A0AEF9000943473 /* AlbyGradient.swift in Sources */,
|
||||||
4C687C272A6039500092C550 /* TestData.swift in Sources */,
|
4C687C272A6039500092C550 /* TestData.swift in Sources */,
|
||||||
@@ -1947,6 +1957,7 @@
|
|||||||
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
|
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
|
||||||
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */,
|
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */,
|
||||||
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
|
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
|
||||||
|
BA4AB0AE2A63B9270070A32A /* AddEmojiView.swift in Sources */,
|
||||||
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */,
|
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */,
|
||||||
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */,
|
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */,
|
||||||
3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */,
|
3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */,
|
||||||
@@ -1976,6 +1987,7 @@
|
|||||||
4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */,
|
4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */,
|
||||||
4C7D09602A098C5D00943473 /* WalletView.swift in Sources */,
|
4C7D09602A098C5D00943473 /* WalletView.swift in Sources */,
|
||||||
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */,
|
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */,
|
||||||
|
BA4AB0B02A63B94D0070A32A /* EmojiListItemView.swift in Sources */,
|
||||||
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
|
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
|
||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
||||||
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */,
|
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */,
|
||||||
|
|||||||
@@ -174,6 +174,12 @@ class UserSettingsStore: ObservableObject {
|
|||||||
@Setting(key: "developer_mode", default_value: false)
|
@Setting(key: "developer_mode", default_value: false)
|
||||||
var developer_mode: Bool
|
var developer_mode: Bool
|
||||||
|
|
||||||
|
@Setting(key: "emoji_reactions", default_value: default_emoji_reactions)
|
||||||
|
var emoji_reactions: [String]
|
||||||
|
|
||||||
|
@Setting(key: "default_emoji_reaction", default_value: "🤙")
|
||||||
|
var default_emoji_reaction: String
|
||||||
|
|
||||||
// Helper for inverse of disable_animation.
|
// Helper for inverse of disable_animation.
|
||||||
// disable_animation was introduced as a setting first, but it's more natural for the settings UI to show the inverse.
|
// disable_animation was introduced as a setting first, but it's more natural for the settings UI to show the inverse.
|
||||||
var enable_animation: Bool {
|
var enable_animation: Bool {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ enum Route: Hashable {
|
|||||||
case NotificationSettings(settings: UserSettingsStore)
|
case NotificationSettings(settings: UserSettingsStore)
|
||||||
case ZapSettings(settings: UserSettingsStore)
|
case ZapSettings(settings: UserSettingsStore)
|
||||||
case TranslationSettings(settings: UserSettingsStore)
|
case TranslationSettings(settings: UserSettingsStore)
|
||||||
|
case ReactionsSettings(settings: UserSettingsStore)
|
||||||
case SearchSettings(settings: UserSettingsStore)
|
case SearchSettings(settings: UserSettingsStore)
|
||||||
case DeveloperSettings(settings: UserSettingsStore)
|
case DeveloperSettings(settings: UserSettingsStore)
|
||||||
case Thread(thread: ThreadModel)
|
case Thread(thread: ThreadModel)
|
||||||
@@ -81,6 +82,8 @@ enum Route: Hashable {
|
|||||||
ZapSettingsView(settings: settings)
|
ZapSettingsView(settings: settings)
|
||||||
case .TranslationSettings(let settings):
|
case .TranslationSettings(let settings):
|
||||||
TranslationSettingsView(settings: settings)
|
TranslationSettingsView(settings: settings)
|
||||||
|
case .ReactionsSettings(let settings):
|
||||||
|
ReactionsSettingsView(settings: settings)
|
||||||
case .SearchSettings(let settings):
|
case .SearchSettings(let settings):
|
||||||
SearchSettingsView(settings: settings)
|
SearchSettingsView(settings: settings)
|
||||||
case .DeveloperSettings(let settings):
|
case .DeveloperSettings(let settings):
|
||||||
@@ -154,6 +157,8 @@ enum Route: Hashable {
|
|||||||
return true
|
return true
|
||||||
case (.SearchSettings, .SearchSettings):
|
case (.SearchSettings, .SearchSettings):
|
||||||
return true
|
return true
|
||||||
|
case (.ReactionsSettings, .ReactionsSettings):
|
||||||
|
return true
|
||||||
case (.DeveloperSettings, .DeveloperSettings):
|
case (.DeveloperSettings, .DeveloperSettings):
|
||||||
return true
|
return true
|
||||||
case (.Thread(let lhs_threadModel), .Thread(thread: let rhs_threadModel)):
|
case (.Thread(let lhs_threadModel), .Thread(thread: let rhs_threadModel)):
|
||||||
@@ -233,6 +238,8 @@ enum Route: Hashable {
|
|||||||
hasher.combine("zapSettings")
|
hasher.combine("zapSettings")
|
||||||
case .TranslationSettings:
|
case .TranslationSettings:
|
||||||
hasher.combine("translationSettings")
|
hasher.combine("translationSettings")
|
||||||
|
case .ReactionsSettings:
|
||||||
|
hasher.combine("reactionsSettings")
|
||||||
case .SearchSettings:
|
case .SearchSettings:
|
||||||
hasher.combine("searchSettings")
|
hasher.combine("searchSettings")
|
||||||
case .DeveloperSettings:
|
case .DeveloperSettings:
|
||||||
|
|||||||
@@ -68,11 +68,11 @@ struct EventActionBar: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
LikeButton(liked: bar.liked) {
|
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 {
|
if bar.liked {
|
||||||
notify(.delete, bar.our_like)
|
notify(.delete, bar.our_like)
|
||||||
} else {
|
} else {
|
||||||
send_like()
|
send_like(emoji: emoji)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +139,9 @@ struct EventActionBar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func send_like() {
|
func send_like(emoji: String) {
|
||||||
guard let keypair = damus_state.keypair.to_full(),
|
guard let keypair = damus_state.keypair.to_full(),
|
||||||
let like_ev = make_like_event(keypair: keypair, liked: event) else {
|
let like_ev = make_like_event(keypair: keypair, liked: event, content: emoji) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +166,17 @@ func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct LikeButton: View {
|
struct LikeButton: View {
|
||||||
|
let damus_state: DamusState
|
||||||
let liked: Bool
|
let liked: Bool
|
||||||
let action: () -> ()
|
let liked_emoji: String?
|
||||||
|
let action: (_ emoji: String) -> Void
|
||||||
|
|
||||||
|
// For reactions background
|
||||||
|
@State private var showReactionsBG = 0
|
||||||
|
@State private var showEmojis: [Int] = []
|
||||||
|
@State private var rotateThumb = -45
|
||||||
|
|
||||||
|
@State private var isReactionsVisible = false
|
||||||
|
|
||||||
// Following four are Shaka animation properties
|
// Following four are Shaka animation properties
|
||||||
let timer = Timer.publish(every: 0.10, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 0.10, on: .main, in: .common).autoconnect()
|
||||||
@@ -175,21 +184,48 @@ struct LikeButton: View {
|
|||||||
@State private var rotationAngle = 0.0
|
@State private var rotationAngle = 0.0
|
||||||
@State private var amountOfAngleIncrease: Double = 0.0
|
@State private var amountOfAngleIncrease: Double = 0.0
|
||||||
|
|
||||||
var body: some View {
|
var emojis: [String] {
|
||||||
|
damus_state.settings.emoji_reactions
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func buildMaskView(for emoji: String) -> some View {
|
||||||
|
if emoji == "🤙" {
|
||||||
|
LINEAR_GRADIENT
|
||||||
|
.mask(
|
||||||
|
Image("shaka.fill")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
likeButton()
|
||||||
|
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
|
||||||
|
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
|
||||||
|
.onReceive(self.timer) { _ in
|
||||||
|
shakaAnimationLogic()
|
||||||
|
}
|
||||||
|
.simultaneousGesture(longPressGesture())
|
||||||
|
.overlay(reactionsOverlay())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func likeButton() -> some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
guard !isReactionsVisible else { return }
|
||||||
withAnimation(Animation.easeOut(duration: 0.15)) {
|
withAnimation(Animation.easeOut(duration: 0.15)) {
|
||||||
self.action()
|
self.action(damus_state.settings.default_emoji_reaction)
|
||||||
shouldAnimate = true
|
shouldAnimate = true
|
||||||
amountOfAngleIncrease = 20.0
|
amountOfAngleIncrease = 20.0
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
if liked {
|
if let liked_emoji {
|
||||||
LINEAR_GRADIENT
|
buildMaskView(for: liked_emoji)
|
||||||
.mask(Image("shaka.fill")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
)
|
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
} else {
|
} else {
|
||||||
Image("shaka")
|
Image("shaka")
|
||||||
@@ -199,23 +235,112 @@ struct LikeButton: View {
|
|||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
|
}
|
||||||
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
|
|
||||||
.onReceive(self.timer) { _ in
|
func shakaAnimationLogic() {
|
||||||
// Shaka animation logic
|
rotationAngle = amountOfAngleIncrease
|
||||||
rotationAngle = amountOfAngleIncrease
|
if amountOfAngleIncrease == 0 {
|
||||||
if amountOfAngleIncrease == 0 {
|
timer.upstream.connect().cancel()
|
||||||
timer.upstream.connect().cancel()
|
return
|
||||||
return
|
}
|
||||||
}
|
amountOfAngleIncrease = -amountOfAngleIncrease
|
||||||
amountOfAngleIncrease = -amountOfAngleIncrease
|
if amountOfAngleIncrease < 0 {
|
||||||
if amountOfAngleIncrease < 0 {
|
amountOfAngleIncrease += 2.5
|
||||||
amountOfAngleIncrease += 2.5
|
} else {
|
||||||
|
amountOfAngleIncrease -= 2.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func longPressGesture() -> some Gesture {
|
||||||
|
LongPressGesture(minimumDuration: 0.5).onEnded { _ in
|
||||||
|
reactionLongPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reactionsOverlay() -> some View {
|
||||||
|
Group {
|
||||||
|
if isReactionsVisible {
|
||||||
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.frame(width: 250, height: 50)
|
||||||
|
.foregroundColor(DamusColors.black)
|
||||||
|
.scaleEffect(Double(showReactionsBG), anchor: .topTrailing)
|
||||||
|
.animation(
|
||||||
|
.interpolatingSpring(stiffness: 170, damping: 15).delay(0.05),
|
||||||
|
value: showReactionsBG
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color.white.opacity(0.2))
|
||||||
|
.frame(width: 250, height: 50)
|
||||||
|
.clipShape(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.overlay(reactions())
|
||||||
|
}
|
||||||
|
.offset(y: -40)
|
||||||
|
.onTapGesture {
|
||||||
|
withAnimation(.easeOut(duration: 0.2)) {
|
||||||
|
isReactionsVisible = false
|
||||||
|
showReactionsBG = 0
|
||||||
|
}
|
||||||
|
showEmojis = []
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
amountOfAngleIncrease -= 2.5
|
EmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reactions() -> some View {
|
||||||
|
HStack {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 20) {
|
||||||
|
ForEach(emojis, id: \.self) { emoji in
|
||||||
|
if let index = emojis.firstIndex(of: emoji) {
|
||||||
|
let scale = showEmojis.count >= index + 1 ? showEmojis[index] : 0
|
||||||
|
Text(emoji)
|
||||||
|
.scaleEffect(Double(scale))
|
||||||
|
.onTapGesture {
|
||||||
|
emojiTapped(emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When reaction button is long pressed, it displays the multiple emojis overlay and displays the user's selected emojis with an animation
|
||||||
|
private func reactionLongPressed() {
|
||||||
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
|
showEmojis = Array(repeating: 0, count: emojis.count) // Initialize the showEmojis array
|
||||||
|
|
||||||
|
for (index, _) in emojis.enumerated() {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 * Double(index)) {
|
||||||
|
withAnimation(.interpolatingSpring(stiffness: 170, damping: 8)) {
|
||||||
|
showEmojis[index] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isReactionsVisible = true
|
||||||
|
showReactionsBG = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private func emojiTapped(_ emoji: String) {
|
||||||
|
print("Tapped emoji: \(emoji)")
|
||||||
|
|
||||||
|
self.action(emoji)
|
||||||
|
|
||||||
|
withAnimation(.easeOut(duration: 0.2)) {
|
||||||
|
isReactionsVisible = false
|
||||||
|
showReactionsBG = 0
|
||||||
|
}
|
||||||
|
showEmojis = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ struct ConfigView: View {
|
|||||||
NavigationLink(value: Route.DeveloperSettings(settings: settings)) {
|
NavigationLink(value: Route.DeveloperSettings(settings: settings)) {
|
||||||
IconLabel(NSLocalizedString("Developer", comment: "Section header for developer settings"), img_name: "magic-stick2.fill", color: DamusColors.adaptableBlack)
|
IconLabel(NSLocalizedString("Developer", comment: "Section header for developer settings"), img_name: "magic-stick2.fill", color: DamusColors.adaptableBlack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NavigationLink(value: Route.ReactionsSettings(settings: settings)) {
|
||||||
|
IconLabel(NSLocalizedString("Reactions", comment: "Section header for reactions settings"), img_name: "shaka.fill", color: .purple)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
||||||
|
|||||||
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