Skip to content

Commit 8f8c8de

Browse files
clauxxibrkhalil
andauthored
Composer collapsing when editing canceled/done (#17785)
* fix: composer height when entering and canceling edit * fix: blur the composer input when canceling edit * fix: focusing animation and composer height after blur * fix: input height when canceling edit while unfocused * ref: removed arbitrary keyboard check * fix: moved edit-mentions logic to use-edit to fix unresolved mention * fix: composer edit should put the cursor at the end * fix: (potentially) fixing the mention not resolved during edit * fix: emoji-kb handler changing the height when default kb appears * Fix text content when editing and reentering chat * prevent composer when focusing on opening chat with edit/reply * clean * Clauxx comments * Apply for reply * Lintil soup = yummy * refactor variable name * Extract the focusing logic from the data setting logic * Edge case * fix: composer mention key & edit re-enter issues * fix: reply cancel input blur and smooth reply focus --------- Co-authored-by: Ibrkhalil <[email protected]>
1 parent 2e0643f commit 8f8c8de

File tree

11 files changed

+168
-115
lines changed

11 files changed

+168
-115
lines changed

src/status_im/chat/models/input.cljs

+9-8
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,15 @@
107107
[{:keys [db] :as cofx} message]
108108
(let [current-chat-id (:current-chat-id db)
109109
text (get-in message [:content :text])]
110-
{:db (-> db
111-
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message]
112-
message)
113-
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil)
114-
(update-in [:chat/inputs current-chat-id :metadata]
115-
dissoc
116-
:sending-image))
117-
:dispatch [:mention/to-input-field text current-chat-id]}))
110+
{:db (-> db
111+
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message]
112+
message)
113+
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil)
114+
(update-in [:chat/inputs current-chat-id :metadata]
115+
dissoc
116+
:sending-image))
117+
:dispatch-n [[:chat.ui/set-chat-input-text nil current-chat-id]
118+
[:mention/to-input-field text current-chat-id]]}))
118119

119120
(rf/defn show-contact-request-input
120121
"Sets reference to previous chat message and focuses on input"

src/status_im2/contexts/chat/composer/edit/view.cljs

+6-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
[utils.re-frame :as rf]))
1212

1313
(defn edit-message
14-
[state]
14+
[{:keys [text-value input-ref]}]
1515
[rn/view
1616
{:style style/container
1717
:accessibility-label :edit-message}
@@ -30,20 +30,18 @@
3030
{:size 24
3131
:icon-only? true
3232
:accessibility-label :edit-cancel-button
33-
:on-press (fn []
34-
(utils/cancel-edit-message state)
35-
(rf/dispatch [:chat.ui/cancel-message-edit]))
33+
:on-press #(utils/cancel-edit-message text-value input-ref)
3634
:type :outline}
3735
:i/close]])
3836

3937
(defn- f-view
40-
[state]
38+
[props]
4139
(let [edit (rf/sub [:chats/edit-message])
4240
height (reanimated/use-shared-value (if edit constants/edit-container-height 0))]
4341
(rn/use-effect #(reanimated/animate height (if edit constants/edit-container-height 0)) [edit])
4442
[reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})}
45-
(when edit [edit-message state])]))
43+
(when edit [edit-message props])]))
4644

4745
(defn view
48-
[state]
49-
[:f> f-view state])
46+
[props]
47+
[:f> f-view props])

src/status_im2/contexts/chat/composer/effects.cljs

+47-37
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[react-native.core :as rn]
77
[react-native.platform :as platform]
88
[react-native.reanimated :as reanimated]
9+
[reagent.core :as reagent]
910
[status-im2.contexts.chat.composer.constants :as constants]
1011
[status-im2.contexts.chat.composer.keyboard :as kb]
1112
[status-im2.contexts.chat.composer.utils :as utils]
@@ -105,53 +106,62 @@
105106

106107
(defn use-edit
107108
[{:keys [input-ref]}
108-
{:keys [text-value saved-cursor-position]}
109-
{:keys [edit]}]
110-
(rn/use-effect
111-
(fn []
112-
(let [edit-text (get-in edit [:content :text])
113-
text-value-count (count @text-value)]
114-
(when (and edit @input-ref)
115-
;; A small setTimeout is necessary to ensure the statement is enqueued and will get executed
116-
;; ASAP.
117-
;; https://github.com/software-mansion/react-native-screens/issues/472
118-
(js/setTimeout #(.focus ^js @input-ref) 250)
119-
(.setNativeProps ^js @input-ref (clj->js {:text edit-text}))
120-
(reset! text-value edit-text)
121-
(reset! saved-cursor-position (if (zero? text-value-count)
122-
(count edit-text)
123-
text-value-count)))))
124-
[(:message-id edit)]))
109+
{:keys [text-value saved-cursor-position cursor-position]}
110+
{:keys [edit input-with-mentions]}
111+
messages-list-on-layout-finished?]
112+
(let [mention? (some #(= :mention (first %)) (seq input-with-mentions))
113+
composer-just-opened? (not @messages-list-on-layout-finished?)]
114+
(rn/use-effect
115+
(fn []
116+
(let [mention-text (reduce (fn [acc item]
117+
(str acc (second item)))
118+
""
119+
input-with-mentions)
120+
edit-text (cond
121+
mention? mention-text
122+
;; NOTE: using text-value for cases when the user
123+
;; leaves the app with an unfinished edit and re-opens
124+
;; the chat.
125+
(and (seq @text-value) composer-just-opened?)
126+
@text-value
127+
:else (get-in edit [:content :text]))
128+
selection-pos (count edit-text)
129+
inject-edit-text (fn []
130+
(reset! text-value edit-text)
131+
(reset! cursor-position selection-pos)
132+
(reset! saved-cursor-position selection-pos)
133+
(when @input-ref
134+
(.setNativeProps ^js @input-ref
135+
(clj->js {:text edit-text}))))]
136+
137+
(when (and edit @input-ref)
138+
;; NOTE: A small setTimeout is necessary to ensure the focus is enqueued and is executed
139+
;; ASAP. Check https://github.com/software-mansion/react-native-screens/issues/472
140+
;;
141+
;; The nested setTimeout is necessary to avoid both `on-focus` and
142+
;; `on-content-size-change` handlers triggering the height animation simultaneously, as
143+
;; this causes a jump in the
144+
;; UI. This way, `on-focus` will trigger first without changing the height, after which
145+
;; `on-content-size-change` will animate the height of the input based on the injected
146+
;; text.
147+
(js/setTimeout #(do (when @messages-list-on-layout-finished? (.focus ^js @input-ref))
148+
(reagent/next-tick inject-edit-text))
149+
600))))
150+
[(:message-id edit)])))
125151

126152
(defn use-reply
127153
[{:keys [input-ref]}
128154
{:keys [container-opacity]}
129-
{:keys [reply]}]
155+
{:keys [reply]}
156+
messages-list-on-layout-finished?]
130157
(rn/use-effect
131158
(fn []
132159
(when reply
133160
(reanimated/animate container-opacity 1))
134-
(when (and reply @input-ref)
135-
(js/setTimeout #(.focus ^js @input-ref) 250)))
161+
(when (and reply @input-ref @messages-list-on-layout-finished?)
162+
(js/setTimeout #(.focus ^js @input-ref) 600)))
136163
[(:message-id reply)]))
137164

138-
(defn edit-mentions
139-
[{:keys [input-ref]} {:keys [text-value cursor-position]} {:keys [input-with-mentions]}]
140-
(rn/use-effect (fn []
141-
(let [input-text (reduce (fn [acc item]
142-
(str acc (second item)))
143-
""
144-
input-with-mentions)]
145-
(reset! text-value input-text)
146-
(reset! cursor-position (count input-text))
147-
(js/setTimeout #(when @input-ref
148-
(.setNativeProps ^js @input-ref
149-
(clj->js {:selection {:start (count input-text)
150-
:end (count
151-
input-text)}})))
152-
300)))
153-
[(some #(= :mention (first %)) (seq input-with-mentions))]))
154-
155165
(defn update-input-mention
156166
[{:keys [input-ref]}
157167
{:keys [text-value]}

src/status_im2/contexts/chat/composer/gesture.cljs

+4-5
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@
4343
(reset! emoji-kb-extra-height nil))
4444
(reset! maximized? false)
4545
(rf/dispatch [:chat.ui/set-input-maximized false])
46-
(when @input-ref
47-
(.blur ^js @input-ref)))
46+
(utils/blur-input input-ref))
4847

4948
(defn bounce-back
5049
[{:keys [height saved-height opacity background-y]}
@@ -96,13 +95,13 @@
9695
max-height
9796
bounded-height
9897
saved-height))
99-
(when @input-ref ; sheet at min-height, collapse keyboard
100-
(.blur ^js @input-ref)))))))
98+
; sheet at min-height, collapse keyboard
99+
(utils/blur-input input-ref))))))
101100
(gesture/on-end (fn []
102101
(let [diff (- (reanimated/get-shared-value height)
103102
(reanimated/get-shared-value saved-height))]
104103
(if @gesture-enabled?
105-
(if (>= diff 0)
104+
(if (and @expanding? (>= diff 0))
106105
(if (> diff constants/drag-threshold)
107106
(maximize state animations dimensions)
108107
(bounce-back animations dimensions starting-opacity))

src/status_im2/contexts/chat/composer/handlers.cljs

+11-8
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
show-floating-scroll-down-button?]
2424
(reset! focused? true)
2525
(rf/dispatch [:chat.ui/set-input-focused true])
26-
(reanimated/animate height (reanimated/get-shared-value last-height))
27-
(reanimated/set-shared-value saved-height (reanimated/get-shared-value last-height))
28-
(reanimated/animate container-opacity 1)
29-
(when (> (reanimated/get-shared-value last-height) (* constants/background-threshold max-height))
30-
(reanimated/animate opacity 1)
31-
(reanimated/set-shared-value background-y 0))
26+
(let [last-height-value (reanimated/get-shared-value last-height)]
27+
(reanimated/animate height last-height-value)
28+
(reanimated/set-shared-value saved-height last-height-value)
29+
(reanimated/animate container-opacity 1)
30+
(when (> last-height-value (* constants/background-threshold max-height))
31+
(reanimated/animate opacity 1)
32+
(reanimated/set-shared-value background-y 0)))
33+
3234
(js/setTimeout #(reset! lock-selection? false) 300)
3335
(when (and (not-empty @text-value) @input-ref)
3436
(.setNativeProps ^js @input-ref
@@ -72,7 +74,7 @@
7274
"Save new text height, expand composer if possible, show background overlay if needed"
7375
[event
7476
{:keys [maximized? lock-layout? text-value]}
75-
{:keys [height saved-height opacity background-y]}
77+
{:keys [height saved-height last-height opacity background-y]}
7678
{:keys [content-height window-height max-height]}
7779
keyboard-shown]
7880
(when keyboard-shown
@@ -87,8 +89,9 @@
8789
max-height)
8890
new-height (min new-height max-height)]
8991
(reset! content-height content-size)
90-
(when (utils/update-height? content-size height max-height maximized?)
92+
(when (utils/update-height? content-size height max-height)
9193
(reanimated/animate height new-height)
94+
(reanimated/set-shared-value last-height new-height)
9295
(reanimated/set-shared-value saved-height new-height))
9396
(when (= new-height max-height)
9497
(reset! maximized? true)

src/status_im2/contexts/chat/composer/keyboard.cljs

+18-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
[react-native.async-storage :as async-storage]
55
[react-native.core :as rn]
66
[react-native.platform :as platform]
7-
[react-native.reanimated :as reanimated]))
7+
[react-native.reanimated :as reanimated]
8+
[status-im2.contexts.chat.composer.utils :as utils]))
89

910
(defn get-kb-height
1011
[curr-height default-height]
@@ -20,18 +21,24 @@
2021
(async-storage/set-item! :kb-default-height (str height)))))
2122

2223
(defn handle-emoji-kb-ios
23-
"Opening emoji KB on iOS while maximized will cause a flicker up and down. This method handles that."
24+
"Opening emoji KB on iOS will cause a flicker up and down due to height differences.
25+
This method handles that by adding the extra difference between the keyboards. When the input is
26+
expanded to a point where the added difference will make the composer go beyond the screen causing a flicker,
27+
we're subtracting the difference so it only reaches the allowed max-height. We're not animating these
28+
changes to make it appear seamless during transitions between keyboard types when maximized."
2429
[event
2530
{:keys [emoji-kb-extra-height]}
26-
{:keys [text-value]}
31+
{:keys [text-value kb-height]}
2732
{:keys [height saved-height]}
2833
{:keys [max-height]}]
29-
(let [start-h (oops/oget event "startCoordinates.height")
30-
end-h (oops/oget event "endCoordinates.height")
31-
diff (- end-h start-h)
32-
max-height-diff (- max-height diff)
33-
curr-text @text-value]
34-
(if (> (reanimated/get-shared-value height) max-height-diff)
34+
(let [start-h (oops/oget event "startCoordinates.height")
35+
end-h (oops/oget event "endCoordinates.height")
36+
diff (- end-h start-h)
37+
max-height-diff (- max-height diff)
38+
curr-text @text-value
39+
bigger-than-default-kb? (> end-h @kb-height)
40+
almost-expanded? (> (reanimated/get-shared-value height) max-height-diff)]
41+
(if (and almost-expanded? bigger-than-default-kb? (pos? diff))
3542
(do
3643
(reanimated/set-shared-value height (- (reanimated/get-shared-value height) diff))
3744
(reanimated/set-shared-value saved-height (- (reanimated/get-shared-value saved-height) diff))
@@ -58,8 +65,8 @@
5865
#(handle-emoji-kb-ios % props state animations dimensions)))
5966
(reset! keyboard-hide-listener (.addListener rn/keyboard
6067
"keyboardDidHide"
61-
#(when (and platform/android? @input-ref)
62-
(.blur ^js @input-ref)))))
68+
#(when platform/android?
69+
(utils/blur-input input-ref)))))
6370

6471
(defn handle-refocus-emoji-kb-ios
6572
[{:keys [saved-emoji-kb-extra-height]}

src/status_im2/contexts/chat/composer/reply/view.cljs

+7-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
[status-im2.constants :as constant]
1010
[status-im2.contexts.chat.composer.constants :as constants]
1111
[status-im2.contexts.chat.composer.reply.style :as style]
12+
[status-im2.contexts.chat.composer.utils :as utils]
1213
[utils.ens.stateofus :as stateofus]
1314
[utils.i18n :as i18n]
1415
[utils.re-frame :as rf]))
@@ -85,7 +86,7 @@
8586
(defn quoted-message
8687
[{:keys [from content-type contentType parsed-text content deleted? deleted-for-me?
8788
album-images-count]}
88-
in-chat-input? pin? recording-audio?]
89+
in-chat-input? pin? recording-audio? input-ref]
8990
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
9091
current-public-key (rf/sub [:multiaccount/public-key])
9192
content-type (or content-type contentType)
@@ -136,7 +137,7 @@
136137
{:icon-only? true
137138
:size 24
138139
:accessibility-label :reply-cancel-button
139-
:on-press #(rf/dispatch [:chat.ui/cancel-message-reply])
140+
:on-press #(utils/cancel-reply-message input-ref)
140141
:type :outline}
141142
:i/close])
142143
(when (and in-chat-input? recording-audio?)
@@ -148,13 +149,13 @@
148149
:style style/gradient}])]))
149150

150151
(defn- f-view
151-
[recording?]
152+
[recording? input-ref]
152153
(let [reply (rf/sub [:chats/reply-message])
153154
height (reanimated/use-shared-value (if reply constants/reply-container-height 0))]
154155
(rn/use-effect #(reanimated/animate height (if reply constants/reply-container-height 0)) [reply])
155156
[reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})}
156-
(when reply [quoted-message reply true false recording?])]))
157+
(when reply [quoted-message reply true false recording? input-ref])]))
157158

158159
(defn view
159-
[{:keys [recording?]}]
160-
[:f> f-view @recording?])
160+
[{:keys [recording?]} input-ref]
161+
[:f> f-view @recording? input-ref])

src/status_im2/contexts/chat/composer/utils.cljs

+24-7
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
(max min-v (min v max-v)))
1717

1818
(defn update-height?
19-
[content-size height max-height maximized?]
20-
(when-not @maximized?
21-
(let [diff (Math/abs (- content-size (reanimated/get-shared-value height)))]
22-
(and (not= (reanimated/get-shared-value height) max-height)
23-
(> diff constants/content-change-threshold)))))
19+
[content-size height max-height]
20+
(let [diff (Math/abs (- content-size (reanimated/get-shared-value height)))]
21+
(and (not= (reanimated/get-shared-value height) max-height)
22+
(> diff constants/content-change-threshold))))
2423

2524
(defn show-top-gradient?
2625
[y lines max-lines gradient-opacity focused?]
@@ -100,10 +99,28 @@
10099
(not reply?)
101100
(not audio?)))
102101

102+
(defn blur-input
103+
[input-ref]
104+
(when @input-ref
105+
(rf/dispatch [:chat.ui/set-input-focused false])
106+
(.blur ^js @input-ref)))
107+
108+
(defn cancel-reply-message
109+
[input-ref]
110+
(js/setTimeout #(blur-input input-ref) 100)
111+
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height])
112+
(rf/dispatch [:chat.ui/cancel-message-reply]))
113+
103114
(defn cancel-edit-message
104-
[{:keys [text-value]}]
115+
[text-value input-ref]
105116
(reset! text-value "")
106-
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height]))
117+
;; NOTE: adding a timeout to assure the input is blurred on the next tick
118+
;; after the `text-value` was cleared. Otherwise the height will be calculated
119+
;; with the old `text-value`, leading to wrong composer height after blur.
120+
(js/setTimeout #(blur-input input-ref) 100)
121+
(.setNativeProps ^js @input-ref (clj->js {:text ""}))
122+
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height])
123+
(rf/dispatch [:chat.ui/cancel-message-edit]))
107124

108125
(defn count-lines
109126
[s]

0 commit comments

Comments
 (0)