@@ -30,10 +30,11 @@ import Yoga.React.Native.MacOS.ScrollView (nativeScrollView)
3030import Yoga.React.Native.MacOS.Sidebar (sidebarLayout)
3131import Yoga.React.Native.MacOS.TextField (nativeTextField)
3232import Yoga.React.Native.MacOS.Types as T
33+import Yoga.React.Native.Pressable (pressable)
3334import Yoga.React.Native.Reanimated as R
3435import Yoga.React.Native.Style as Style
3536
36−type Reaction = { emoji :: String, count :: Int }
37+type Reaction = { emoji :: String, count :: Int, mine :: Boolean }
3738
3839type Message =
3940 { sender :: String
@@ -90,8 +91,12 @@ customEmojiMap = emojiMap
9091
9192addReaction :: String -> Array Reaction -> Array Reaction
9293addReaction emoji rs = case filter (\r -> r.emoji == emoji) rs of
93− [] -> snoc rs { emoji, count: 1 }
94− _ -> map (\r -> if r.emoji == emoji then r { count = r.count + 1 } else r) rs
94+ [] -> snoc rs { emoji, count: 1, mine: true }
95+ _ -> map (\r -> if r.emoji == emoji then r { count = r.count + 1, mine = true } else r) rs
96+
97+removeReaction :: String -> Array Reaction -> Array Reaction
98+removeReaction emoji rs = filter (\r -> r.count > 0) $
99+ map (\r -> if r.emoji == emoji && r.mine then r { count = r.count - 1, mine = false } else r) rs
95100
96101mockRooms :: Array Room
97102mockRooms =
@@ -107,40 +112,43 @@ mockMessages room = map (\m -> m { body = replaceEmoji m.body }) (mockMessagesRa
107112noReactions :: Array Reaction
108113noReactions = []
109114
115+mkReaction :: String -> Int -> Reaction
116+mkReaction emoji count = { emoji, count, mine: false }
117+
110118mockMessagesRaw :: String -> Array Message
111119mockMessagesRaw = case _ of
112120 "general" ->
113− [ { sender: "Alice", body: "Hey everyone! Welcome to the chat demo :wave:", isMine: false, reactions: [ { emoji: "👋", count: 3 } ], replyTo: Nothing }
114− , { sender: "Bob", body: "This is built with :ps: + React Native macOS", isMine: false, reactions: [ { emoji: "🔥", count: 2 }, { emoji: "❤️", count: 1 } ], replyTo: Nothing }
121+ [ { sender: "Alice", body: "Hey everyone! Welcome to the chat demo :wave:", isMine: false, reactions: [ mkReaction "👋" 3 ], replyTo: Nothing }
122+ , { sender: "Bob", body: "This is built with :ps: + React Native macOS", isMine: false, reactions: [ mkReaction "🔥" 2, mkReaction "❤️" 1 ], replyTo: Nothing }
115123 , { sender: "You", body: "That's awesome! :fire:", isMine: true, reactions: noReactions, replyTo: Just 1 }
116124 , { sender: "Alice", body: "Try typing :rocket: or :heart: in a message", isMine: false, reactions: noReactions, replyTo: Nothing }
117− , { sender: "You", body: "The bubbles look great :sparkles:", isMine: true, reactions: [ { emoji: "👍", count: 2 } ], replyTo: Nothing }
125+ , { sender: "You", body: "The bubbles look great :sparkles:", isMine: true, reactions: [ mkReaction "👍" 2 ], replyTo: Nothing }
118126 , { sender: "Bob", body: "Click the smiley to react!", isMine: false, reactions: noReactions, replyTo: Just 4 }
119− , { sender: "Alice", body: ":party: :shipit:", isMine: false, reactions: [ { emoji: "🎉", count: 5 } ], replyTo: Nothing }
120− , { sender: "Bob", body: ":parrot: Party Parrot! :parrot:", isMine: false, reactions: [ { emoji: "🦜", count: 3 } ], replyTo: Nothing }
127+ , { sender: "Alice", body: ":party: :shipit:", isMine: false, reactions: [ mkReaction "🎉" 5 ], replyTo: Nothing }
128+ , { sender: "Bob", body: ":parrot: Party Parrot! :parrot:", isMine: false, reactions: [ mkReaction "🦜" 3 ], replyTo: Nothing }
121129 , { sender: "You", body: ":dealwithit: deal with it :doge:", isMine: true, reactions: noReactions, replyTo: Nothing }
122130 , { sender: "Alice", body: ":typingcat:", isMine: false, reactions: noReactions, replyTo: Nothing }
123131 ]
124132 "purescript" ->
125− [ { sender: "Phil", body: "Has anyone tried the new compiler release?", isMine: false, reactions: [ { emoji: "👍", count: 4 } ], replyTo: Nothing }
133+ [ { sender: "Phil", body: "Has anyone tried the new compiler release?", isMine: false, reactions: [ mkReaction "👍" 4 ], replyTo: Nothing }
126134 , { sender: "You", body: "Yes! The build times are much better", isMine: true, reactions: noReactions, replyTo: Just 0 }
127− , { sender: "Phil", body: "Row polymorphism makes FFI so clean", isMine: false, reactions: [ { emoji: "❤️", count: 3 } ], replyTo: Nothing }
135+ , { sender: "Phil", body: "Row polymorphism makes FFI so clean", isMine: false, reactions: [ mkReaction "❤️" 3 ], replyTo: Nothing }
128136 , { sender: "Jordan", body: "The ecosystem keeps getting better", isMine: false, reactions: noReactions, replyTo: Nothing }
129− , { sender: "You", body: "Agreed, especially for React Native", isMine: true, reactions: [ { emoji: "🔥", count: 1 } ], replyTo: Just 2 }
130− , { sender: "Jordan", body: ":opensourceparrot: :ps: :haskell: :opensourceparrot:", isMine: false, reactions: [ { emoji: "❤️", count: 4 } ], replyTo: Nothing }
137+ , { sender: "You", body: "Agreed, especially for React Native", isMine: true, reactions: [ mkReaction "🔥" 1 ], replyTo: Just 2 }
138+ , { sender: "Jordan", body: ":opensourceparrot: :ps: :haskell: :opensourceparrot:", isMine: false, reactions: [ mkReaction "❤️" 4 ], replyTo: Nothing }
131139 ]
132140 "react-native" ->
133− [ { sender: "Christoph", body: "macOS support is looking solid", isMine: false, reactions: [ { emoji: "🔥", count: 3 } ], replyTo: Nothing }
141+ [ { sender: "Christoph", body: "macOS support is looking solid", isMine: false, reactions: [ mkReaction "🔥" 3 ], replyTo: Nothing }
134142 , { sender: "You", body: "Native controls feel right at home", isMine: true, reactions: noReactions, replyTo: Just 0 }
135143 , { sender: "Christoph", body: "NSOutlineView, NSSplitView, toolbars...", isMine: false, reactions: noReactions, replyTo: Nothing }
136− , { sender: "You", body: "Even the vibrancy effects work", isMine: true, reactions: [ { emoji: "😮", count: 2 } ], replyTo: Nothing }
137− , { sender: "Christoph", body: "🚀", isMine: false, reactions: [ { emoji: "🚀", count: 7 } ], replyTo: Nothing }
138− , { sender: "You", body: ":fastparrot: :congaparrot: :ultraparrot: conga line!", isMine: true, reactions: [ { emoji: "🎉", count: 3 } ], replyTo: Nothing }
144+ , { sender: "You", body: "Even the vibrancy effects work", isMine: true, reactions: [ mkReaction "😮" 2 ], replyTo: Nothing }
145+ , { sender: "Christoph", body: "🚀", isMine: false, reactions: [ mkReaction "🚀" 7 ], replyTo: Nothing }
146+ , { sender: "You", body: ":fastparrot: :congaparrot: :ultraparrot: conga line!", isMine: true, reactions: [ mkReaction "🎉" 3 ], replyTo: Nothing }
139147 ]
140148 "random" ->
141149 [ { sender: "Eve", body: "Anyone here?", isMine: false, reactions: noReactions, replyTo: Nothing }
142− , { sender: "You", body: "👋", isMine: true, reactions: [ { emoji: "👋", count: 1 } ], replyTo: Just 0 }
143− , { sender: "Eve", body: ":partyblob: :partyblob: :partyblob:", isMine: false, reactions: [ { emoji: "🎉", count: 2 } ], replyTo: Nothing }
150+ , { sender: "You", body: "👋", isMine: true, reactions: [ mkReaction "👋" 1 ], replyTo: Just 0 }
151+ , { sender: "Eve", body: ":partyblob: :partyblob: :partyblob:", isMine: false, reactions: [ mkReaction "🎉" 2 ], replyTo: Nothing }
144152 ]
145153 _ -> []
146154
@@ -260,16 +268,35 @@ chatDemo = component "ChatDemo" \dp -> React.do
260268 ]
261269 )
262270
263− reactionPills msg =
271+ toggleReaction idx emoji = do
272+ setMessages \msgs -> fromMaybe msgs
273+ ( modifyAt idx
274+ ( \m ->
275+ let
276+ existing = filter (\rx -> rx.emoji == emoji) m.reactions
277+ in
278+ case existing !! 0 of
279+ Just rx | rx.mine -> m { reactions = removeReaction emoji m.reactions }
280+ _ -> m { reactions = addReaction emoji m.reactions }
281+ )
282+ msgs
283+ )
284+
285+ reactionPills idx msg =
264286 if length msg.reactions == 0 then mempty
265287 else view { style: tw "flex-row mt-1" <> Style.style { alignSelf: if msg.isMine then "flex-end" else "flex-start" } }
266− ( msg.reactions <#> \r ->
267− view
268− { style: tw "flex-row items-center rounded-full px-2 py-0.5 mr-1"
269− <> Style.style { backgroundColor: reactionBg }
288+ ( msg.reactions <#> \rx ->
289+ pressable
290+ { onPress: handler_ (toggleReaction idx rx.emoji)
270291 }
271− [ text { style: Style.style { fontSize: 12.0 } } r.emoji
272− , text { style: tw "text-xs ml-1" <> Style.style { color: dp.dimFg } } (show r.count)
292+ [ view
293+ { style: tw "flex-row items-center rounded-full px-2 py-0.5 mr-1"
294+ <> Style.style
295+ { backgroundColor: if rx.mine then sentBubbleBg else reactionBg }
296+ }
297+ [ text { style: Style.style { fontSize: 12.0 } } rx.emoji
298+ , text { style: tw "text-xs ml-1" <> Style.style { color: if rx.mine then "#FFFFFF" else dp.dimFg } } (show rx.count)
299+ ]
273300 ]
274301 )
275302
@@ -405,7 +432,7 @@ chatDemo = component "ChatDemo" \dp -> React.do
405432 ]
406433 , if not msg.isMine then reactionPicker idx else mempty
407434 ]
408− , reactionPills msg
435+ , reactionPills idx msg
409436 ]
410437 ]
411438 ]