diff --git a/android/app/src/main/res/drawable-hdpi/icon_close_white.png b/android/app/src/main/res/drawable-hdpi/icon_close_white.png new file mode 100644 index 0000000000..79f55e700a Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-hdpi/icon_dollar_green.png new file mode 100644 index 0000000000..32cf51e421 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_drag_white.png b/android/app/src/main/res/drawable-hdpi/icon_drag_white.png new file mode 100644 index 0000000000..6a079b6f6f Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_close_white.png b/android/app/src/main/res/drawable-mdpi/icon_close_white.png new file mode 100644 index 0000000000..956a2c04fb Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-mdpi/icon_dollar_green.png new file mode 100644 index 0000000000..fc6f77f756 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_drag_white.png b/android/app/src/main/res/drawable-mdpi/icon_drag_white.png new file mode 100644 index 0000000000..7845600020 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_close_white.png b/android/app/src/main/res/drawable-xhdpi/icon_close_white.png new file mode 100644 index 0000000000..5c8005f904 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-xhdpi/icon_dollar_green.png new file mode 100644 index 0000000000..cdea0d4af4 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_drag_white.png b/android/app/src/main/res/drawable-xhdpi/icon_drag_white.png new file mode 100644 index 0000000000..2b200673bb Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_close_white.png b/android/app/src/main/res/drawable-xxhdpi/icon_close_white.png new file mode 100644 index 0000000000..32e84becb6 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-xxhdpi/icon_dollar_green.png new file mode 100644 index 0000000000..dac30955fe Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_drag_white.png b/android/app/src/main/res/drawable-xxhdpi/icon_drag_white.png new file mode 100644 index 0000000000..23fa971c5e Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_drag_white.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_close_white.png b/android/app/src/main/res/drawable-xxxhdpi/icon_close_white.png new file mode 100644 index 0000000000..68dbc7fb36 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_close_white.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_dollar_green.png b/android/app/src/main/res/drawable-xxxhdpi/icon_dollar_green.png new file mode 100644 index 0000000000..1f3c1c57d1 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_dollar_green.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_drag_white.png b/android/app/src/main/res/drawable-xxxhdpi/icon_drag_white.png new file mode 100644 index 0000000000..fd3f08e9ae Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_drag_white.png differ diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 0d0af3a2f3..1ddb552cc0 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -3,6 +3,7 @@ [status-im.models.commands :as commands] [clojure.string :as str] [status-im.components.styles :refer [default-chat-color]] + [status-im.chat.styles.response :refer [request-info-height response-height-normal]] [status-im.chat.suggestions :as suggestions] [status-im.protocol.api :as api] [status-im.models.messages :as messages] @@ -15,8 +16,13 @@ [status-im.utils.handlers :as u] [status-im.persistence.realm :as r] [status-im.handlers.server :as server] + [status-im.handlers.content-suggestions :refer [get-content-suggestions]] [status-im.utils.phone-number :refer [format-phone-number]] - [status-im.utils.datetime :as time])) + [status-im.utils.datetime :as time] + [status-im.chat.handlers.animation :refer [update-response-height + get-response-height]])) + +(def delta 1) (register-handler :set-show-actions (fn [db [_ show-actions]] @@ -41,9 +47,22 @@ (assoc-in [:chats current-chat-id :command-input] {}) (update-in [:chats current-chat-id :input-text] safe-trim)))) +(register-handler :start-cancel-command + (u/side-effect! + (fn [db _] + (if (commands/get-chat-command-to-msg-id db) + (dispatch [:animate-cancel-command]) + (dispatch [:cancel-command]))))) + (register-handler :set-chat-command-content - (fn [db [_ content]] - (commands/set-chat-command-content db content))) + (fn [{:keys [current-chat-id] :as db} [_ content]] + (as-> db db + (commands/set-chat-command-content db content) + (assoc-in db [:chats current-chat-id :input-text] nil) + (if (commands/get-chat-command-to-msg-id db) + (do (dispatch [:animate-response-resize]) + (update-response-height db)) + db)))) (defn update-input-text [{:keys [current-chat-id] :as db} text] @@ -51,7 +70,7 @@ (register-handler :stage-command (fn [{:keys [current-chat-id] :as db} _] - (let [db (update-input-text db nil) + (let [db (update-input-text db nil) {:keys [command content]} (get-in db [:chats current-chat-id :command-input]) command-info {:command command @@ -59,7 +78,25 @@ :handler (:handler command)}] (commands/stage-command db command-info)))) +(register-handler :set-message-input [] + (fn [db [_ input]] + (assoc db :message-input input))) + +(register-handler :prepare-message-input + (u/side-effect! + (fn [db _] + (when-let [message-input (:message-input db)] + (.clear message-input) + (.focus message-input))))) + +(register-handler :blur-message-input + (u/side-effect! + (fn [db _] + (when-let [message-input (:message-input db)] + (.blur message-input))))) + (register-handler :set-response-chat-command + (after #(dispatch [:animate-show-response])) (fn [db [_ to-msg-id command-key]] (commands/set-response-chat-command db to-msg-id command-key))) @@ -68,8 +105,12 @@ (update-input-text db text)) (defn update-command [db [_ text]] - (let [{:keys [command]} (suggestions/check-suggestion db text)] - (commands/set-chat-command db command))) + (if-not (commands/get-chat-command db) + (let [{:keys [command]} (suggestions/check-suggestion db text)] + (if command + (commands/set-chat-command db command) + db)) + db)) (register-handler :set-chat-input-text ((enrich update-command) update-text)) diff --git a/src/status_im/chat/handlers/animation.cljs b/src/status_im/chat/handlers/animation.cljs new file mode 100644 index 0000000000..a7526ec19f --- /dev/null +++ b/src/status_im/chat/handlers/animation.cljs @@ -0,0 +1,111 @@ +(ns status-im.chat.handlers.animation + (:require [re-frame.core :refer [register-handler after dispatch]] + [re-frame.middleware :refer [path]] + [status-im.models.commands :as commands] + [status-im.handlers.content-suggestions :refer [get-content-suggestions]] + [status-im.chat.styles.message-input :refer [input-height]] + [status-im.chat.styles.response :refer [request-info-height response-height-normal]] + [status-im.chat.styles.response-suggestions :as response-suggestions-styles] + [status-im.constants :refer [response-input-hiding-duration]])) + +(def zero-height input-height) + +(register-handler :finish-animate-cancel-command + (fn [db _] + (assoc-in db [:animations :commands-input-is-switching?] false))) + +(register-handler :animate-cancel-command + (path :animations) + (fn [db _] + (if-not (:commands-input-is-switching? db) + (assoc db + :commands-input-is-switching? true + :message-input-buttons-scale 1 + :message-input-offset 0 + :to-response-height zero-height + :messages-offset 0) + db))) + +(register-handler :finish-animate-response-resize + (fn [db _] + (let [fixed (get-in db [:animations :to-response-height])] + (-> db + (assoc-in [:animations :response-height-current] fixed) + (assoc-in [:animations :response-resize?] false))))) + +(register-handler :set-response-height + (fn [db [_ value]] + (assoc-in db [:animations :response-height-current] value))) + +(register-handler :animate-response-resize + (fn [db _] + (assoc-in db [:animations :response-resize?] true))) + +(defn get-response-height [db] + (let [command (commands/get-chat-command db) + text (commands/get-chat-command-content db) + suggestions (get-content-suggestions command text) + suggestions-height (reduce + 0 (map #(if (:header %) + response-suggestions-styles/header-height + response-suggestions-styles/suggestion-height) + suggestions))] + (+ zero-height + (min response-height-normal (+ suggestions-height request-info-height))))) + +(defn update-response-height [db] + (assoc-in db [:animations :to-response-height] (get-response-height db))) + +(register-handler :finish-show-response + (after #(dispatch [:prepare-message-input])) + (fn [db _] + (assoc-in db [:animations :commands-input-is-switching?] false))) + +(register-handler :animate-show-response + (after #(dispatch [:animate-response-resize])) + (fn [db _] + (-> db + (assoc-in [:animations :commands-input-is-switching?] true) + (assoc-in [:animations :response-height-current] zero-height) + (assoc-in [:animations :message-input-buttons-scale] 0.1) + (assoc-in [:animations :message-input-offset] -40) + (assoc-in [:animations :messages-offset] request-info-height) + (update-response-height)))) + +(register-handler :set-response-max-height + (fn [db [_ height]] + (let [prev-height (get-in db [:animations :response-height-max])] + (if (not= height prev-height) + (let [db (assoc-in db [:animations :response-height-max] height)] + (if (= prev-height (get-in db [:animations :to-response-height])) + (-> db + (assoc-in [:animations :to-response-height] height) + (assoc-in [:animations :response-height-current] height)) + db)) + db)))) + +(register-handler :on-drag-response + (fn [db [_ dy]] + (let [fixed (get-in db [:animations :to-response-height])] + (-> db + (assoc-in [:animations :response-height-current] (- fixed dy)) + (assoc-in [:animations :response-resize?] false))))) + +(register-handler :fix-response-height + (fn [db _] + (if (and (commands/get-chat-command-to-msg-id db) + (not (get-in db [:animations :commands-input-is-switching?]))) + (let [current (get-in db [:animations :response-height-current]) + normal-height response-height-normal + command (commands/get-chat-command db) + text (commands/get-chat-command-content db) + suggestions (get-content-suggestions command text) + max-height (get-in db [:animations :response-height-max]) + delta (/ normal-height 2) + new-fixed (cond + (or (<= current (+ zero-height delta)) + (empty? suggestions)) (+ zero-height request-info-height) + (<= current (+ zero-height normal-height delta)) (get-response-height db) + :else max-height)] + (dispatch [:animate-response-resize]) + (assoc-in db [:animations :to-response-height] new-fixed)) + db))) diff --git a/src/status_im/chat/screen.cljs b/src/status_im/chat/screen.cljs index 3ff250e238..a2fc957b1e 100644 --- a/src/status_im/chat/screen.cljs +++ b/src/status_im/chat/screen.cljs @@ -3,6 +3,7 @@ (:require [re-frame.core :refer [subscribe dispatch]] [clojure.string :as s] [status-im.components.react :refer [view + animated-view text image icon @@ -17,8 +18,13 @@ [status-im.components.invertible-scroll-view :refer [invertible-scroll-view]] [status-im.components.toolbar :refer [toolbar]] [status-im.chat.views.message :refer [chat-message]] + [status-im.chat.views.content-suggestions :refer [content-suggestions-view]] + [status-im.chat.views.suggestions :refer [suggestions-view]] + [status-im.chat.views.response :refer [response-view]] [status-im.chat.views.new-message :refer [chat-message-new]] - [status-im.i18n :refer [label label-pluralize]])) + [status-im.i18n :refer [label label-pluralize]] + [status-im.components.animation :as anim] + [reagent.core :as r])) (defn contacts-by-identity [contacts] @@ -213,21 +219,58 @@ :custom-action [toolbar-action]}]))) (defview messages-view [group-chat] - [messages [:chat :messages] - contacts [:chat :contacts]] - (let [contacts' (contacts-by-identity contacts)] - [list-view {:renderRow (message-row contacts' group-chat) - :renderScrollComponent #(invertible-scroll-view (js->clj %)) - :onEndReached #(dispatch [:load-more-messages]) - :enableEmptySections true - :dataSource (to-datasource messages)}])) + [messages [:chat :messages] + contacts [:chat :contacts]] + (let [contacts' (contacts-by-identity contacts)] + [list-view {:renderRow (message-row contacts' group-chat) + :renderScrollComponent #(invertible-scroll-view (js->clj %)) + :onEndReached #(dispatch [:load-more-messages]) + :enableEmptySections true + :keyboardShouldPersistTaps true + :dataSource (to-datasource messages)}])) + +(defn messages-container-animation-logic [{:keys [to-value val]}] + (fn [_] + (let [to-value @to-value] + (anim/start (anim/spring val {:toValue to-value}) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-in [:animations ::messages-offset-current] to-value]))))))) + +(defn messages-container [messages] + (let [to-messages-offset (subscribe [:get-in [:animations :messages-offset]]) + cur-messages-offset (subscribe [:get-in [:animations ::messages-offset-current]]) + messages-offset (anim/create-value (or @cur-messages-offset 0)) + context {:to-value to-messages-offset + :val messages-offset} + on-update (messages-container-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [messages] + @to-messages-offset + [animated-view {:style (st/messages-container messages-offset)} + messages])}))) (defview chat [] [group-chat [:chat :group-chat] - show-actions-atom [:show-actions]] - [view st/chat-view + show-actions-atom [:show-actions] + command [:get-chat-command] + to-msg-id [:get-chat-command-to-msg-id]] + [view {:style st/chat-view + :onLayout (fn [event] + (let [height (.. event -nativeEvent -layout -height)] + (dispatch [:set-response-max-height height])))} [chat-toolbar] - [messages-view group-chat] + [messages-container + [messages-view group-chat]] (when group-chat [typing-all]) + (cond + (and command to-msg-id) [response-view] + command [content-suggestions-view] + :else [suggestions-view]) [chat-message-new] (when show-actions-atom [actions-view])]) diff --git a/src/status_im/chat/styles/content_suggestions.cljs b/src/status_im/chat/styles/content_suggestions.cljs index 198468a549..47ee5fc1fb 100644 --- a/src/status_im/chat/styles/content_suggestions.cljs +++ b/src/status_im/chat/styles/content_suggestions.cljs @@ -44,6 +44,9 @@ :backgroundColor color-white :borderRadius 5}) +(def container + {:backgroundColor color-white}) + (def drag-down-touchable {:height 22 :alignItems :center diff --git a/src/status_im/chat/styles/input.cljs b/src/status_im/chat/styles/input.cljs index 474cd2a115..53e02cb16e 100644 --- a/src/status_im/chat/styles/input.cljs +++ b/src/status_im/chat/styles/input.cljs @@ -37,7 +37,8 @@ (def command-input {:flex 1 :marginLeft 8 - :marginTop 7 + :marginTop -2 + :padding 0 :fontSize 14 :fontFamily font :color text1-color}) diff --git a/src/status_im/chat/styles/message_input.cljs b/src/status_im/chat/styles/message_input.cljs new file mode 100644 index 0000000000..9cbcc4e27f --- /dev/null +++ b/src/status_im/chat/styles/message_input.cljs @@ -0,0 +1,32 @@ +(ns status-im.chat.styles.message-input + (:require [status-im.components.styles :refer [color-white + color-blue]])) + +(def input-height 56) + +(defn message-input-container [offset] + {:flex 1 + :transform [{:translateX offset}] + :marginRight offset}) + +(def input-container + {:flexDirection :column}) + +(def input-view + {:flexDirection :row + :height input-height + :backgroundColor color-white}) + +(def send-icon + {:marginTop 10.5 + :marginLeft 12 + :width 15 + :height 15}) + +(def send-container + {:marginTop 10 + :marginRight 10 + :width 36 + :height 36 + :borderRadius 50 + :backgroundColor color-blue}) diff --git a/src/status_im/chat/styles/plain_input.cljs b/src/status_im/chat/styles/plain_input.cljs deleted file mode 100644 index b8806518dd..0000000000 --- a/src/status_im/chat/styles/plain_input.cljs +++ /dev/null @@ -1,55 +0,0 @@ -(ns status-im.chat.styles.plain-input - (:require [status-im.components.styles :refer [font - text2-color - color-white - color-blue]])) - -(def input-container - {:flexDirection :column}) - -(def input-view - {:flexDirection :row - :height 56 - :backgroundColor color-white}) - -(def switch-commands-touchable - {:width 56 - :height 56 - :alignItems :center - :justifyContent :center}) - -(def list-icon - {:width 13 - :height 12}) - -(def close-icon - {:width 12 - :height 12}) - -(def message-input - {:flex 1 - :marginTop -2 - :padding 0 - :fontSize 14 - :fontFamily font - :color text2-color}) - -(def smile-icon - {:marginTop 18 - :marginRight 18 - :width 20 - :height 20}) - -(def send-icon - {:marginTop 10.5 - :marginLeft 12 - :width 15 - :height 15}) - -(def send-container - {:marginTop 10 - :marginRight 10 - :width 36 - :height 36 - :borderRadius 50 - :backgroundColor color-blue}) diff --git a/src/status_im/chat/styles/plain_message.cljs b/src/status_im/chat/styles/plain_message.cljs new file mode 100644 index 0000000000..d52ae4d257 --- /dev/null +++ b/src/status_im/chat/styles/plain_message.cljs @@ -0,0 +1,32 @@ +(ns status-im.chat.styles.plain-message + (:require [status-im.components.styles :refer [font + text2-color]])) + +(def message-input-button-touchable + {:width 56 + :height 56 + :alignItems :center + :justifyContent :center}) + +(defn message-input-button [scale] + {:transform [{:scale scale}]}) + +(def list-icon + {:width 13 + :height 12}) + +(def close-icon + {:width 12 + :height 12}) + +(def message-input + {:flex 1 + :marginTop -2 + :padding 0 + :fontSize 14 + :fontFamily font + :color text2-color}) + +(def smile-icon + {:width 20 + :height 20}) diff --git a/src/status_im/chat/styles/response.cljs b/src/status_im/chat/styles/response.cljs new file mode 100644 index 0000000000..029ca2ed86 --- /dev/null +++ b/src/status_im/chat/styles/response.cljs @@ -0,0 +1,96 @@ +(ns status-im.chat.styles.response + (:require [status-im.components.styles :refer [font + color-white + color-blue + text1-color + text2-color + chat-background + color-black]] + [status-im.chat.styles.message-input :refer [input-height]])) + +(def response-height-normal 211) +(def request-info-height 61) + +(def drag-container + {:height 16 + :alignItems :center + :justifyContent :center}) + +(def drag-icon + {:width 14 + :height 3}) + +(def command-icon-container + {:marginTop 1 + :marginLeft 12 + :width 32 + :height 32 + :alignItems :center + :justifyContent :center + :borderRadius 50 + :backgroundColor color-white}) + +(def command-icon + {:width 9 + :height 15}) + +(def info-container + {:flex 1 + :marginLeft 12}) + +(def command-name + {:marginTop 0 + :fontSize 12 + :fontFamily font + :color color-white}) + +(def message-info + {:marginTop 1 + :fontSize 12 + :fontFamily font + :opacity 0.69 + :color color-white}) + +(defn response-view [height] + {:flexDirection :column + :position :absolute + :left 0 + :right 0 + :bottom 0 + :height height + :backgroundColor color-white + :elevation 2}) + +(def input-placeholder + {:height input-height}) + +(defn request-info [color] + {:flexDirection :column + :height request-info-height + :backgroundColor color}) + +(def inner-container + {:flexDirection :row + :height 45}) + +(def cancel-container + {:marginTop 2.5 + :marginRight 16 + :width 24 + :height 24}) + +(def cancel-icon + {:marginTop 6 + :marginLeft 6 + :width 12 + :height 12}) + +(def command-input + {:flex 1 + :marginLeft 56 + :marginRight 16 + :marginTop -2 + :padding 0 + :fontSize 14 + :fontFamily font + :color text1-color}) diff --git a/src/status_im/chat/styles/response_suggestions.cljs b/src/status_im/chat/styles/response_suggestions.cljs new file mode 100644 index 0000000000..2561f2ae53 --- /dev/null +++ b/src/status_im/chat/styles/response_suggestions.cljs @@ -0,0 +1,59 @@ +(ns status-im.chat.styles.response-suggestions + (:require [status-im.components.styles :refer [font + font-medium + color-light-blue-transparent + color-white + color-black + color-blue + color-blue-transparent + selected-message-color + online-color + separator-color + text1-color + text2-color + text3-color]])) + +(def header-height 50) +(def suggestion-height 56) + +(def header-container + {:paddingLeft 16 + :height header-height + :backgroundColor color-white}) + +(def header-text + {:marginTop 18 + :fontSize 13 + :fontFamily font-medium + :color text2-color}) + +(def suggestion-container + {:flexDirection :column + :paddingLeft 16 + :height suggestion-height + :backgroundColor color-white}) + +(def suggestion-sub-container + {:height suggestion-height + :borderBottomWidth 1 + :borderBottomColor separator-color}) + +(def value-text + {:marginTop 10 + :fontSize 12 + :fontFamily font + :color text1-color}) + +(def description-text + {:marginTop 2 + :fontSize 12 + :fontFamily font + :color text2-color}) + +(def suggestions-container + {:flexDirection :row + :flex 1 + :marginVertical 1 + :marginHorizontal 0 + :backgroundColor color-white + :borderRadius 5}) diff --git a/src/status_im/chat/styles/screen.cljs b/src/status_im/chat/styles/screen.cljs index 35e4d0b3d7..49133f8d32 100644 --- a/src/status_im/chat/styles/screen.cljs +++ b/src/status_im/chat/styles/screen.cljs @@ -14,6 +14,10 @@ {:flex 1 :backgroundColor chat-background}) +(defn messages-container [bottom] + {:flex 1 + :bottom bottom}) + (def toolbar-view {:flexDirection :row :height 56 diff --git a/src/status_im/chat/styles/suggestions.cljs b/src/status_im/chat/styles/suggestions.cljs index 2d5eaaab8e..0c35347f79 100644 --- a/src/status_im/chat/styles/suggestions.cljs +++ b/src/status_im/chat/styles/suggestions.cljs @@ -59,6 +59,9 @@ :backgroundColor color-white :borderRadius 5}) +(def container + {:backgroundColor color-white}) + (def drag-down-touchable {:height 22 :alignItems :center diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index d5df5f5716..9b11d29fd0 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -1,11 +1,12 @@ (ns status-im.chat.subs (:require-macros [reagent.ratom :refer [reaction]]) - (:require [re-frame.core :refer [register-sub]] + (:require [re-frame.core :refer [register-sub dispatch]] [status-im.db :as db] ;todo handlers in subs?... [status-im.chat.suggestions :refer - [get-suggestions typing-command? get-content-suggestions]] + [get-suggestions typing-command?]] [status-im.models.commands :as commands] + [status-im.constants :refer [response-suggesstion-resize-duration]] [status-im.handlers.content-suggestions :refer [get-content-suggestions]])) (register-sub :chat-properties @@ -70,6 +71,10 @@ (fn [db _] (reaction (commands/get-chat-command-content @db)))) +(register-sub :get-chat-command-to-msg-id + (fn [db _] + (reaction (commands/get-chat-command-to-msg-id @db)))) + (register-sub :chat-command-request (fn [db _] (reaction (commands/get-chat-command-request @db)))) @@ -90,5 +95,5 @@ (register-sub :get-content-suggestions (fn [db _] (let [command (reaction (commands/get-chat-command @db)) - text (reaction (commands/get-chat-command-content @db))] - (reaction (get-content-suggestions @db @command @text))))) + text (reaction (commands/get-chat-command-content @db))] + (reaction (get-content-suggestions @command @text))))) diff --git a/src/status_im/chat/views/command.cljs b/src/status_im/chat/views/command.cljs index 2b550dc7bf..7ed7faafc3 100644 --- a/src/status_im/chat/views/command.cljs +++ b/src/status_im/chat/views/command.cljs @@ -1,16 +1,15 @@ (ns status-im.chat.views.command + (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [view - icon - text - text-input - touchable-highlight]] - [status-im.chat.views.content-suggestions :refer - [content-suggestions-view]] + icon + text + text-input + touchable-highlight]] [status-im.chat.styles.input :as st])) (defn cancel-command-input [] - (dispatch [:cancel-command])) + (dispatch [:start-cancel-command])) (defn set-input-message [message] (dispatch [:set-chat-command-content message])) @@ -24,28 +23,17 @@ (validator message) (pos? (count message)))) -(defn simple-command-input-view [command input-options & {:keys [validator]}] - (let [message-atom (subscribe [:get-chat-command-content])] - (fn [command input-options & {:keys [validator]}] - (let [message @message-atom] - [view st/command-input-and-suggestions-container - [content-suggestions-view] - [view st/command-input-container - [view (st/command-text-container command) - [text {:style st/command-text} (:text command)]] - [text-input (merge {:style st/command-input - :autoFocus true - :onChangeText set-input-message - :onSubmitEditing (fn [] - (when (valid? message validator) - (send-command))) - :accessibility-label :command-input} - input-options) - message] - (if (valid? message validator) - [touchable-highlight {:on-press send-command - :accessibility-label :stage-command} - [view st/send-container [icon :send st/send-icon]]] - [touchable-highlight {:on-press cancel-command-input} - [view st/cancel-container - [icon :close-gray st/cancel-icon]]])]])))) +(defn try-send [message validator] + (when (valid? message validator) + (send-command))) + +(defn command-icon [command] + [view (st/command-text-container command) + [text {:style st/command-text} (:text command)]]) + +(defview cancel-button [] + [commands-input-is-switching? [:get-in [:animations :commands-input-is-switching?]]] + [touchable-highlight {:disabled commands-input-is-switching? + :on-press cancel-command-input} + [view st/cancel-container + [icon :close-gray st/cancel-icon]]]) diff --git a/src/status_im/chat/views/confirmation_code.cljs b/src/status_im/chat/views/confirmation_code.cljs deleted file mode 100644 index b1d5f4c94e..0000000000 --- a/src/status_im/chat/views/confirmation_code.cljs +++ /dev/null @@ -1,6 +0,0 @@ -(ns status-im.chat.views.confirmation-code - (:require - [status-im.chat.views.command :refer [simple-command-input-view]])) - -(defn confirmation-code-input-view [command] - [simple-command-input-view command {:keyboardType :numeric}]) diff --git a/src/status_im/chat/views/content_suggestions.cljs b/src/status_im/chat/views/content_suggestions.cljs index 20fb1df9e5..4dfefe2cf4 100644 --- a/src/status_im/chat/views/content_suggestions.cljs +++ b/src/status_im/chat/views/content_suggestions.cljs @@ -2,11 +2,11 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [view - icon - text - touchable-highlight - list-view - list-item]] + icon + text + touchable-highlight + list-view + list-item]] [status-im.chat.styles.content-suggestions :as st] [status-im.utils.listview :refer [to-datasource]])) @@ -25,12 +25,13 @@ (defview content-suggestions-view [] [suggestions [:get-content-suggestions]] - (when (seq suggestions) - [view + (when-let [values (not-empty (filter :value suggestions))] + [view st/container [touchable-highlight {:style st/drag-down-touchable ;; TODO hide suggestions? :onPress (fn [])} [view [icon :drag_down st/drag-down-icon]]] - [view (st/suggestions-container (count suggestions)) - [list-view {:dataSource (to-datasource suggestions) - :renderRow render-row}]]])) + [view (st/suggestions-container (count values)) + [list-view {:dataSource (to-datasource values) + :keyboardShouldPersistTaps true + :renderRow render-row}]]])) diff --git a/src/status_im/chat/views/message_input.cljs b/src/status_im/chat/views/message_input.cljs new file mode 100644 index 0000000000..c8d81a30b6 --- /dev/null +++ b/src/status_im/chat/views/message_input.cljs @@ -0,0 +1,127 @@ +(ns status-im.chat.views.message-input + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + icon + touchable-highlight + text-input + dismiss-keyboard!]] + [status-im.components.animation :as anim] + [status-im.chat.views.plain-message :as plain-message] + [status-im.chat.views.command :as command] + [status-im.chat.views.response :as response] + [status-im.chat.styles.message-input :as st] + [status-im.chat.styles.plain-message :as st-message] + [status-im.chat.styles.input :as st-command] + [status-im.chat.styles.response :as st-response] + [status-im.constants :refer [response-input-hiding-duration]])) + +(defview send-button [{:keys [on-press accessibility-label]}] + [commands-input-is-switching? [:get-in [:animations :commands-input-is-switching?]]] + [touchable-highlight {:disabled commands-input-is-switching? + :on-press on-press + :accessibility-label accessibility-label} + [view st/send-container + [icon :send st/send-icon]]]) + +(defn message-input-container-animation-logic [{:keys [to-value val]}] + (fn [_] + (let [to-value @to-value] + (anim/start (anim/timing val {:toValue to-value + :duration response-input-hiding-duration}) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-in [:animations ::message-input-offset-current] to-value]))))))) + +(defn message-input-container [input] + (let [to-message-input-offset (subscribe [:get-in [:animations :message-input-offset]]) + cur-message-input-offset (subscribe [:get-in [:animations ::message-input-offset-current]]) + message-input-offset (anim/create-value (or @cur-message-input-offset 0)) + context {:to-value to-message-input-offset + :val message-input-offset} + on-update (message-input-container-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [input] + @to-message-input-offset + [animated-view {:style (st/message-input-container message-input-offset)} + input])}))) + +(defview message-input [input-options validator] + [input-message [:get-chat-input-text] + command [:get-chat-command] + to-msg-id [:get-chat-command-to-msg-id] + input-command [:get-chat-command-content] + staged-commands [:get-chat-staged-commands] + typing-command? [:typing-command?] + commands-input-is-switching? [:get-in [:animations :commands-input-is-switching?]]] + (let [dismiss-keyboard (not (or command typing-command?)) + response? (and command to-msg-id) + message-input? (or (not command) commands-input-is-switching?) + animation? commands-input-is-switching?] + [text-input (merge {:style (cond + message-input? st-message/message-input + response? st-response/command-input + command st-command/command-input) + :ref (fn [input] + (dispatch [:set-message-input input])) + :autoFocus false + :blurOnSubmit dismiss-keyboard + :onChangeText (fn [text] + (when-not animation? + ((if message-input? + plain-message/set-input-message + command/set-input-message) + text))) + :onSubmitEditing #(when-not animation? + (if message-input? + (plain-message/try-send staged-commands + input-message + dismiss-keyboard) + (command/try-send input-command validator)))} + (when command + {:accessibility-label :command-input}) + input-options) + (if message-input? + input-message + input-command)])) + +(defview plain-message-input-view [{:keys [input-options validator]}] + [input-message [:get-chat-input-text] + command [:get-chat-command] + to-msg-id [:get-chat-command-to-msg-id] + input-command [:get-chat-command-content] + staged-commands [:get-chat-staged-commands] + typing-command? [:typing-command?] + commands-input-is-switching? [:get-in [:animations :commands-input-is-switching?]]] + (let [dismiss-keyboard (not (or command typing-command?)) + response? (and command to-msg-id) + message-input? (or (not command) commands-input-is-switching?)] + [view st/input-container + [view st/input-view + (if message-input? + [plain-message/commands-button] + (when (and command (not response?)) + [command/command-icon command response?])) + [message-input-container + [message-input input-options validator]] + ;; TODO emoticons: not implemented + (when message-input? + [plain-message/smile-button]) + (if message-input? + (when (plain-message/message-valid? staged-commands input-message) + [send-button {:on-press #(plain-message/try-send staged-commands + input-message + dismiss-keyboard) + :accessibility-label :send-message}]) + (if (command/valid? input-command validator) + [send-button {:on-press command/send-command + :accessibility-label :stage-command}] + (when-not response? + [command/cancel-button])))]])) diff --git a/src/status_im/chat/views/money.cljs b/src/status_im/chat/views/money.cljs deleted file mode 100644 index 0051b597f6..0000000000 --- a/src/status_im/chat/views/money.cljs +++ /dev/null @@ -1,7 +0,0 @@ -(ns status-im.chat.views.money - (:require - [status-im.chat.views.command :refer [simple-command-input-view]])) - -(defn money-input-view [command] - [simple-command-input-view command - {:keyboardType :numeric}]) diff --git a/src/status_im/chat/views/new_message.cljs b/src/status_im/chat/views/new_message.cljs index 29b203a067..be01687c74 100644 --- a/src/status_im/chat/views/new_message.cljs +++ b/src/status_im/chat/views/new_message.cljs @@ -1,14 +1,11 @@ (ns status-im.chat.views.new-message + (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe]] [status-im.components.react :refer [view]] - [status-im.chat.views.plain-input :refer [plain-message-input-view]] - [status-im.chat.views.command :refer [simple-command-input-view]] - [status-im.chat.views.phone :refer [phone-input-view]] - [status-im.chat.views.password :refer [password-input-view]] - [status-im.chat.views.confirmation-code :refer [confirmation-code-input-view]] - [status-im.chat.views.money :refer [money-input-view]] + [status-im.chat.views.message-input :refer [plain-message-input-view]] [status-im.chat.views.staged-command :refer [simple-command-staged-view]] + [status-im.utils.phone-number :refer [valid-mobile-number?]] [status-im.chat.styles.message :as st])) (defn staged-command-view [stage-command] @@ -19,27 +16,24 @@ (for [command staged-commands] ^{:key command} [staged-command-view command])]) -(defn default-command-input-view [command] - [simple-command-input-view command {}]) - -(defn special-input-view [command] - (case (:command command) - :phone [phone-input-view command] - :keypair-password [password-input-view command] - :confirmation-code [confirmation-code-input-view command] - :money [money-input-view command] - :request [money-input-view command] - [default-command-input-view command])) +(defn show-input [command] + [plain-message-input-view + (when command + (case (:command command) + :phone {:input-options {:keyboardType :phone-pad} + :validator valid-mobile-number?} + :keypair-password {:input-options {:secureTextEntry true}} + :confirmation-code {:input-options {:keyboardType :numeric}} + :money {:input-options {:keyboardType :numeric}} + :request {:input-options {:keyboardType :numeric}} + (throw (js/Error. "Uknown command type"))))]) (defn chat-message-new [] - (let [command-atom (subscribe [:get-chat-command]) + (let [command-atom (subscribe [:get-chat-command]) staged-commands-atom (subscribe [:get-chat-staged-commands])] (fn [] - (let [command @command-atom - staged-commands @staged-commands-atom] + (let [staged-commands @staged-commands-atom] [view st/new-message-container (when (and staged-commands (pos? (count staged-commands))) [staged-commands-view staged-commands]) - (if command - [special-input-view command] - [plain-message-input-view])])))) + [show-input @command-atom]])))) diff --git a/src/status_im/chat/views/password.cljs b/src/status_im/chat/views/password.cljs deleted file mode 100644 index cc87e05fff..0000000000 --- a/src/status_im/chat/views/password.cljs +++ /dev/null @@ -1,7 +0,0 @@ -(ns status-im.chat.views.password - (:require - [status-im.chat.views.command - :refer [simple-command-input-view]])) - -(defn password-input-view [command] - [simple-command-input-view command {:secureTextEntry true}]) diff --git a/src/status_im/chat/views/phone.cljs b/src/status_im/chat/views/phone.cljs deleted file mode 100644 index f74ad65568..0000000000 --- a/src/status_im/chat/views/phone.cljs +++ /dev/null @@ -1,9 +0,0 @@ -(ns status-im.chat.views.phone - (:require - [status-im.chat.views.command - :refer [simple-command-input-view]] - [status-im.utils.phone-number :refer [valid-mobile-number?]])) - -(defn phone-input-view [command] - [simple-command-input-view command {:keyboardType :phone-pad} - :validator valid-mobile-number?]) diff --git a/src/status_im/chat/views/plain_input.cljs b/src/status_im/chat/views/plain_input.cljs deleted file mode 100644 index dcbc75780a..0000000000 --- a/src/status_im/chat/views/plain_input.cljs +++ /dev/null @@ -1,54 +0,0 @@ -(ns status-im.chat.views.plain-input - (:require [re-frame.core :refer [subscribe dispatch]] - [status-im.components.react :refer [view - icon - touchable-highlight - text-input]] - [status-im.chat.views.suggestions :refer [suggestions-view]] - [status-im.chat.styles.plain-input :as st])) - -(defn set-input-message [message] - (dispatch [:set-chat-input-text message])) - -(defn send [chat input-message] - (let [{:keys [group-chat chat-id]} chat] - (dispatch [:send-chat-msg]))) - -(defn message-valid? [staged-commands message] - (or (and (pos? (count message)) - (not= "!" message)) - (pos? (count staged-commands)))) - -(defn try-send [chat staged-commands message] - (when (message-valid? staged-commands message) - (send chat message))) - -(defn plain-message-input-view [] - (let [chat (subscribe [:get-current-chat]) - input-message-atom (subscribe [:get-chat-input-text]) - staged-commands-atom (subscribe [:get-chat-staged-commands]) - typing-command? (subscribe [:typing-command?])] - (fn [] - (let [input-message @input-message-atom] - [view st/input-container - [suggestions-view] - [view st/input-view - [touchable-highlight {:on-press #(dispatch [:switch-command-suggestions]) - :style st/switch-commands-touchable} - [view nil - (if @typing-command? - [icon :close-gray st/close-icon] - [icon :list st/list-icon])]] - [text-input {:style st/message-input - :autoFocus (pos? (count @staged-commands-atom)) - :onChangeText set-input-message - :onSubmitEditing #(try-send @chat @staged-commands-atom - input-message)} - input-message] - ;; TODO emoticons: not implemented - [icon :smile st/smile-icon] - (when (message-valid? @staged-commands-atom input-message) - [touchable-highlight {:on-press #(send @chat input-message) - :accessibility-label :send-message} - [view st/send-container - [icon :send st/send-icon]]])]])))) diff --git a/src/status_im/chat/views/plain_message.cljs b/src/status_im/chat/views/plain_message.cljs new file mode 100644 index 0000000000..b516c64dc9 --- /dev/null +++ b/src/status_im/chat/views/plain_message.cljs @@ -0,0 +1,94 @@ +(ns status-im.chat.views.plain-message + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + icon + touchable-highlight + dismiss-keyboard!]] + [status-im.components.animation :as anim] + [status-im.chat.styles.plain-message :as st] + [status-im.constants :refer [response-input-hiding-duration]])) + +(defn set-input-message [message] + (dispatch [:set-chat-input-text message])) + +(defn send [dismiss-keyboard] + (when dismiss-keyboard + (dismiss-keyboard!)) + (dispatch [:send-chat-msg])) + +(defn message-valid? [staged-commands message] + (or (and (pos? (count message)) + (not= "!" message)) + (pos? (count staged-commands)))) + +(defn try-send [staged-commands message dismiss-keyboard] + (when (message-valid? staged-commands message) + (send dismiss-keyboard))) + +(defn commands-button-animation-logic [{:keys [to-value val]}] + (fn [_] + (let [to-scale @to-value + minimum 0.1 + scale (cond (< 1 to-scale) 1 + (< to-scale minimum) minimum + :else to-scale)] + (anim/start (anim/timing val {:toValue scale + :duration response-input-hiding-duration}) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-in [:animations ::message-input-buttons-scale-current] scale]) + (when (= to-scale minimum) + (dispatch [:finish-show-response])))))))) + +(defn commands-button [] + (let [typing-command? (subscribe [:typing-command?]) + animation? (subscribe [:get-in [:animations :commands-input-is-switching?]]) + to-scale (subscribe [:get-in [:animations :message-input-buttons-scale]]) + cur-scale (subscribe [:get-in [:animations ::message-input-buttons-scale-current]]) + buttons-scale (anim/create-value (or @cur-scale 1)) + context {:to-value to-scale + :val buttons-scale} + on-update (commands-button-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [] + (let [typing-command? @typing-command?] + @to-scale + [touchable-highlight {:disabled @animation? + :on-press #(dispatch [:switch-command-suggestions]) + :style st/message-input-button-touchable} + [animated-view {:style (st/message-input-button buttons-scale)} + (if typing-command? + [icon :close-gray st/close-icon] + [icon :list st/list-icon])]]))}))) + +(defn smile-button [] + (let [animation? (subscribe [:get-in [:animations :commands-input-is-switching?]]) + to-scale (subscribe [:get-in [:animations :message-input-buttons-scale]]) + cur-scale (subscribe [:get-in [:animations ::message-input-buttons-scale-current]]) + buttons-scale (anim/create-value (or @cur-scale 1)) + context {:to-value to-scale + :val buttons-scale} + on-update (commands-button-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [] + @to-scale + [touchable-highlight {:disabled @animation? + :on-press (fn [] + ;; TODO emoticons: not implemented + ) + :style st/message-input-button-touchable} + [animated-view {:style (st/message-input-button buttons-scale)} + [icon :smile st/smile-icon]]])}))) diff --git a/src/status_im/chat/views/response.cljs b/src/status_im/chat/views/response.cljs new file mode 100644 index 0000000000..061403ca3a --- /dev/null +++ b/src/status_im/chat/views/response.cljs @@ -0,0 +1,99 @@ +(ns status-im.chat.views.response + (:require-macros [reagent.ratom :refer [reaction]] + [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + icon + image + text + text-input + touchable-highlight]] + [status-im.components.drag-drop :as drag] + [status-im.chat.views.response-suggestions :refer [response-suggestions-view]] + [status-im.chat.styles.response :as st] + [status-im.chat.styles.message-input :refer [input-height]] + [status-im.components.animation :as anim])) + +(defn drag-icon [] + [view st/drag-container + [icon :drag-white st/drag-icon]]) + +(defn command-icon [] + [view st/command-icon-container + ;; TODO stub data: command icon + [icon :dollar-green st/command-icon]]) + +(defn info-container [command] + [view st/info-container + [text {:style st/command-name} + (:description command)] + [text {:style st/message-info} + ;; TODO stub data: request message info + "By ???, MMM 1st at HH:mm"]]) + +(defn create-response-pan-responder [] + (drag/create-pan-responder + {:on-move (fn [e gesture] + (dispatch [:on-drag-response (.-dy gesture)])) + :on-release (fn [e gesture] + (dispatch [:fix-response-height]))})) + +(defn request-info [] + (let [pan-responder (create-response-pan-responder) + command (subscribe [:get-chat-command])] + (fn [] + [view (merge (drag/pan-handlers pan-responder) + {:style (st/request-info (:color @command))}) + [drag-icon] + [view st/inner-container + [command-icon nil] + [info-container @command] + [touchable-highlight {:on-press #(dispatch [:start-cancel-command])} + [view st/cancel-container + [icon :close-white st/cancel-icon]]]]]))) + +(defn container-animation-logic [{:keys [animation? to-value current-value val]}] + (fn [_] + (if @animation? + (let [to-value @to-value] + (anim/start (anim/spring val {:toValue to-value}) + (fn [arg] + (when (.-finished arg) + (dispatch [:set-in [:animations :response-height-current] to-value]) + (dispatch [:finish-animate-response-resize]) + (when (= to-value input-height) + (dispatch [:finish-animate-cancel-command]) + (dispatch [:cancel-command])))))) + (anim/set-value val @current-value)))) + +(defn container [& children] + (let [commands-input-is-switching? (subscribe [:get-in [:animations :commands-input-is-switching?]]) + response-resize? (subscribe [:get-in [:animations :response-resize?]]) + to-response-height (subscribe [:get-in [:animations :to-response-height]]) + cur-response-height (subscribe [:get-in [:animations :response-height-current]]) + response-height (anim/create-value (or @cur-response-height 0)) + context {:animation? (reaction (or @commands-input-is-switching? @response-resize?)) + :to-value to-response-height + :current-value cur-response-height + :val response-height} + on-update (container-animation-logic context)] + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [& children] + @to-response-height + (into [animated-view {:style (st/response-view (if (or @commands-input-is-switching? @response-resize?) + response-height + (or @cur-response-height 0)))}] + children))}))) + +(defn response-view [] + [container + [request-info] + [response-suggestions-view] + [view st/input-placeholder]]) diff --git a/src/status_im/chat/views/response_suggestions.cljs b/src/status_im/chat/views/response_suggestions.cljs new file mode 100644 index 0000000000..54eed3bb7d --- /dev/null +++ b/src/status_im/chat/views/response_suggestions.cljs @@ -0,0 +1,38 @@ +(ns status-im.chat.views.response-suggestions + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [status-im.components.react :refer [view + icon + text + touchable-highlight + list-view + list-item]] + [status-im.chat.styles.response-suggestions :as st] + [status-im.utils.listview :refer [to-datasource]])) + +(defn set-command-content [content] + (dispatch [:set-chat-command-content content])) + +(defn header-list-item [{:keys [header]}] + [view st/header-container + [text {:style st/header-text} header]]) + +(defn suggestion-list-item [{:keys [value description]}] + [touchable-highlight {:onPress #(set-command-content value)} + [view st/suggestion-container + [view st/suggestion-sub-container + [text {:style st/value-text} value] + [text {:style st/description-text} description]]]]) + +(defn render-row [row _ _] + (list-item (if (:header row) + [header-list-item row] + [suggestion-list-item row]))) + +(defview response-suggestions-view [] + [suggestions [:get-content-suggestions]] + (when (seq suggestions) + [view st/suggestions-container + [list-view {:dataSource (to-datasource suggestions) + :keyboardShouldPersistTaps true + :renderRow render-row}]])) diff --git a/src/status_im/chat/views/suggestions.cljs b/src/status_im/chat/views/suggestions.cljs index 9a170cd5a5..e0f261542a 100644 --- a/src/status_im/chat/views/suggestions.cljs +++ b/src/status_im/chat/views/suggestions.cljs @@ -1,11 +1,11 @@ (ns status-im.chat.views.suggestions (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [view - text - icon - touchable-highlight - list-view - list-item]] + text + icon + touchable-highlight + list-view + list-item]] [status-im.utils.listview :refer [to-datasource]] [status-im.chat.styles.suggestions :as st])) @@ -33,7 +33,7 @@ (fn [] (let [suggestions @suggestions-atom] (when (seq suggestions) - [view + [view st/container [touchable-highlight {:style st/drag-down-touchable :onPress (fn [] ;; TODO hide suggestions? @@ -41,6 +41,7 @@ [view [icon :drag_down st/drag-down-icon]]] [view (st/suggestions-container (count suggestions)) - [list-view {:dataSource (to-datasource suggestions) - :enableEmptySections true - :renderRow render-row}]]]))))) + [list-view {:dataSource (to-datasource suggestions) + :enableEmptySections true + :keyboardShouldPersistTaps true + :renderRow render-row}]]]))))) diff --git a/src/status_im/chats_list/views/inner_item.cljs b/src/status_im/chats_list/views/inner_item.cljs index 783578c3a1..2803b5d6e4 100644 --- a/src/status_im/chats_list/views/inner_item.cljs +++ b/src/status_im/chats_list/views/inner_item.cljs @@ -7,7 +7,7 @@ [status-im.utils.datetime :as time])) (defn chat-list-item-inner-view - [{:keys [chat-id name color photo-path new-messages-count + [{:keys [chat-id name color new-messages-count online group-chat contacts] :as chat}] (let [last-message (first (:messages chat))] [view st/chat-container diff --git a/src/status_im/components/animation.cljs b/src/status_im/components/animation.cljs new file mode 100644 index 0000000000..677223b338 --- /dev/null +++ b/src/status_im/components/animation.cljs @@ -0,0 +1,45 @@ +(ns status-im.components.animation + (:require [status-im.components.react :refer [animated]])) + +(defn start + ([anim] (.start anim)) + ([anim callback] (.start anim callback))) + +(defn timing [anim-value config] + (.timing animated anim-value (clj->js config))) + +(defn spring [anim-value config] + (.spring animated anim-value (clj->js config))) + +(defn event [config] + (.event animated (clj->js [nil, config]))) + +(defn add-listener [anim-value listener] + (.addListener anim-value listener)) + +(defn remove-all-listeners [anim-value] + (.removeAllListeners anim-value)) + +(defn stop-animation [anim-value] + (.stopAnimation anim-value)) + +(defn value [anim-value] + (.-value anim-value)) + +(defn set-value [anim-value value] + (.setValue anim-value value)) + +(defn create-value [value] + (js/React.Animated.Value. value)) + +(defn x [value-xy] + (.-x value-xy)) + +(defn y [value-xy] + (.-y value-xy)) + +(defn get-layout [value-xy] + (js->clj (.getLayout value-xy))) + +(defn create-value-xy [x y] + (js/React.Animated.ValueXY. (clj->js {:x x, :y y}))) diff --git a/src/status_im/components/drag_drop.cljs b/src/status_im/components/drag_drop.cljs new file mode 100644 index 0000000000..708efdfb12 --- /dev/null +++ b/src/status_im/components/drag_drop.cljs @@ -0,0 +1,12 @@ +(ns status-im.components.drag-drop + (:require [status-im.components.react :refer [animated pan-responder]] + [status-im.components.animation :as anim])) + +(defn pan-handlers [pan-responder] + (js->clj (.-panHandlers pan-responder))) + +(defn create-pan-responder [{:keys [on-move on-release]}] + (.create pan-responder + (clj->js {:onStartShouldSetPanResponder (fn [] true) + :onPanResponderMove on-move + :onPanResponderRelease on-release}))) diff --git a/src/status_im/components/react.cljs b/src/status_im/components/react.cljs index 0ab2d2b755..1347f22ea2 100644 --- a/src/status_im/components/react.cljs +++ b/src/status_im/components/react.cljs @@ -46,6 +46,14 @@ (when-let [picker (get-react-property "Picker")] (adapt-class (.-Item picker)))) +(def pan-responder (.-PanResponder js/React)) +(def animated (.-Animated js/React)) +(def animated-view (r/adapt-react-class (.-View animated))) +(def animated-text (r/adapt-react-class (.-Text animated))) + +(def dimensions (.-Dimensions js/React)) +(defn get-dimensions [name] + (js->clj (.get dimensions name) :keywordize-keys true)) (defn icon ([n] (icon n {})) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index a684182899..556f9b7898 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -10,3 +10,8 @@ (def content-type-command "command") (def content-type-command-request "command-request") (def content-type-status "status") + +(def max-chat-name-length 20) + +(def response-input-hiding-duration 300) +(def response-suggesstion-resize-duration 100) diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 283a09a1a9..2b3ad6f88f 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -1,5 +1,7 @@ (ns status-im.db - (:require [schema.core :as s :include-macros true])) + (:require [schema.core :as s :include-macros true] + [status-im.components.react :refer [animated]] + [status-im.components.animation :as anim])) ;; schema of app-db (def schema {:greeting s/Str}) @@ -34,7 +36,15 @@ :address "" :whisper-identity "" :phone-number ""} - :disable-group-creation false}) + :disable-group-creation false + :animations {;; mutable data + :to-response-height nil + :response-height-current nil + :message-input-offset 0 + :message-input-buttons-scale 1 + :messages-offset 0 + :commands-input-is-switching? false + :response-resize? false}}) (def protocol-initialized-path [:protocol-initialized]) (defn chat-input-text-path [chat-id] diff --git a/src/status_im/group_settings/screen.cljs b/src/status_im/group_settings/screen.cljs index 346b783912..a35bdbcf53 100644 --- a/src/status_im/group_settings/screen.cljs +++ b/src/status_im/group_settings/screen.cljs @@ -146,7 +146,9 @@ (defview chat-name [] [name [:chat :name] new-name [:get :new-chat-name] - focused? [:get ::name-input-focused]] + validation-messages [:new-chat-name-validation-messages] + focused? [:get ::name-input-focused] + valid? [:new-chat-name-valid?]] [view [text {:style st/chat-name-text} (label :t/chat-name)] [view (st/chat-name-value-container focused?) @@ -157,12 +159,14 @@ :on-blur blur} name] (if (or focused? (not= name new-name)) - [touchable-highlight {:style st/chat-name-btn-edit-container + [touchable-highlight {:style (st/chat-name-btn-edit-container valid?) :on-press save} [view [icon :ok-purple st/add-members-icon]]] - [touchable-highlight {:style st/chat-name-btn-edit-container + [touchable-highlight {:style (st/chat-name-btn-edit-container true) :on-press focus} - [text {:style st/chat-name-btn-edit-text} (label :t/edit)]])]]) + [text {:style st/chat-name-btn-edit-text} (label :t/edit)]])] + (when (pos? (count validation-messages)) + [text {:style st/chat-name-validation-message} (first validation-messages)])]) (defview group-settings [] [show-color-picker [:group-settings :show-color-picker]] diff --git a/src/status_im/group_settings/styles/group_settings.cljs b/src/status_im/group_settings/styles/group_settings.cljs index 91d407c604..9950bb2f9c 100644 --- a/src/status_im/group_settings/styles/group_settings.cljs +++ b/src/status_im/group_settings/styles/group_settings.cljs @@ -91,9 +91,15 @@ :fontFamily font :color text1-color}) -(def chat-name-btn-edit-container +(def chat-name-validation-message + {:marginTop 8 + :marginLeft 16 + :color :red}) + +(defn chat-name-btn-edit-container [enabled?] {:padding 16 - :justifyContent :center}) + :justifyContent :center + :opacity (if enabled? 1 0.3)}) (def chat-name-btn-edit-text {:marginTop -1 diff --git a/src/status_im/group_settings/subs.cljs b/src/status_im/group_settings/subs.cljs index f2b6ac1db3..c1bb17da4d 100644 --- a/src/status_im/group_settings/subs.cljs +++ b/src/status_im/group_settings/subs.cljs @@ -1,6 +1,7 @@ (ns status-im.group-settings.subs (:require-macros [reagent.ratom :refer [reaction]]) - (:require [re-frame.core :refer [register-sub]])) + (:require [re-frame.core :refer [register-sub]] + [status-im.constants :refer [max-chat-name-length]])) (register-sub :selected-participant (fn [db _] @@ -11,3 +12,20 @@ (register-sub :group-settings (fn [db [_ k]] (reaction (get-in @db [:group-settings k])))) + +(defn get-chat-name-validation-messages [chat-name] + (filter some? + (list (when (zero? (count chat-name)) + "Chat name can't be empty") + (when (< max-chat-name-length (count chat-name)) + "Chat name is too long")))) + +(register-sub :new-chat-name-validation-messages + (fn [db [_]] + (let [chat-name (reaction (:new-chat-name @db))] + (reaction (get-chat-name-validation-messages @chat-name))))) + +(register-sub :new-chat-name-valid? + (fn [db [_]] + (let [chat-name (reaction (:new-chat-name @db))] + (reaction (zero? (count (get-chat-name-validation-messages @chat-name))))))) diff --git a/src/status_im/handlers/content_suggestions.cljs b/src/status_im/handlers/content_suggestions.cljs index f4457f931f..d43801cff3 100644 --- a/src/status_im/handlers/content_suggestions.cljs +++ b/src/status_im/handlers/content_suggestions.cljs @@ -4,19 +4,22 @@ [status-im.utils.logging :as log] [clojure.string :as s])) +;; TODO stub data? (def suggestions - {:phone [{:value "89171111111" + {:phone [{:header "Phone number formats"} + {:value "89171111111" :description "Number format 1"} - {:value "+79171111111" + {:value "+79171111111" :description "Number format 2"} - {:value "9171111111" + {:value "9171111111" :description "Number format 3"}]}) -(defn get-content-suggestions [db command text] +(defn get-content-suggestions [command text] (or (when command (when-let [command-suggestions ((:command command) suggestions)] (filterv (fn [s] - (and (.startsWith (:value s) (or text "")) - (not= (:value s) text))) + (or (:header s) + (and (.startsWith (:value s) (or text "")) + (not= (:value s) text)))) command-suggestions))) [])) diff --git a/src/status_im/models/commands.cljs b/src/status_im/models/commands.cljs index a9ba2ffd26..b02c18ef28 100644 --- a/src/status_im/models/commands.cljs +++ b/src/status_im/models/commands.cljs @@ -3,6 +3,7 @@ [clojure.walk :refer [stringify-keys keywordize-keys]] [re-frame.core :refer [subscribe dispatch]] [status-im.db :as db] + [status-im.components.animation :as anim] [status-im.components.styles :refer [color-blue color-dark-mint]] [status-im.i18n :refer [label]])) diff --git a/src/status_im/navigation/handlers.cljs b/src/status_im/navigation/handlers.cljs index c47134e429..ac5bd9ec12 100644 --- a/src/status_im/navigation/handlers.cljs +++ b/src/status_im/navigation/handlers.cljs @@ -48,7 +48,8 @@ (fn [db _] (-> db (push-view :new-group) - (assoc :new-group #{}))))) + (assoc :new-group #{}) + (assoc :new-chat-name nil))))) (register-handler :show-contacts (fn [db _] diff --git a/src/status_im/new_group/screen.cljs b/src/status_im/new_group/screen.cljs index 4b266486c6..9e81f77b89 100644 --- a/src/status_im/new_group/screen.cljs +++ b/src/status_im/new_group/screen.cljs @@ -3,13 +3,13 @@ (:require [re-frame.core :refer [subscribe dispatch]] [status-im.resources :as res] [status-im.components.react :refer [view - text-input - text - image - icon - touchable-highlight - list-view - list-item]] + text-input + text + image + icon + touchable-highlight + list-view + list-item]] [status-im.components.styles :refer [color-purple]] [status-im.components.toolbar :refer [toolbar]] [status-im.utils.listview :refer [to-datasource]] @@ -19,24 +19,30 @@ (defview new-group-toolbar [] - [group-name [:get ::group-name] - creation-disabled? [:get :disable-group-creation]] - [toolbar - {:title (label :t/new-group-chat) - :action {:image {:source res/v ;; {:uri "icon_search"} - :style st/toolbar-icon} - :handler (when-not creation-disabled? - #(dispatch [:init-group-creation group-name]))}}]) + [group-name [:get :new-chat-name] + creation-disabled? [:get :disable-group-creation] + valid? [:new-chat-name-valid?]] + (let [create-btn-enabled? (and valid? (not creation-disabled?))] + [toolbar + {:title (label :t/new-group-chat) + :action {:image {:source res/v ;; {:uri "icon_search"} + :style (st/toolbar-icon create-btn-enabled?)} + :handler (when create-btn-enabled? + #(dispatch [:init-group-creation group-name]))}}])) (defview group-name-input [] - [group-name [:get ::group-name]] - [text-input - {:underlineColorAndroid color-purple - :style st/group-name-input - :autoFocus true - :placeholder (label :t/group-name) - :onChangeText #(dispatch [:set ::group-name %])} - group-name]) + [group-name [:get :new-chat-name] + validation-messages [:new-chat-name-validation-messages]] + [view + [text-input + {:underlineColorAndroid color-purple + :style st/group-name-input + :autoFocus true + :placeholder (label :t/group-name) + :onChangeText #(dispatch [:set :new-chat-name %])} + group-name] + (when (pos? (count validation-messages)) + [text {:style st/group-name-validation-message} (first validation-messages)])]) (defview new-group [] [contacts [:all-contacts]] diff --git a/src/status_im/new_group/styles.cljs b/src/status_im/new_group/styles.cljs index cd16b8dcc8..3d15673453 100644 --- a/src/status_im/new_group/styles.cljs +++ b/src/status_im/new_group/styles.cljs @@ -7,9 +7,10 @@ text2-color toolbar-background1]])) -(def toolbar-icon - {:width 20 - :height 18}) +(defn toolbar-icon [enabled?] + {:width 20 + :height 18 + :opacity (if enabled? 1 0.3)}) (def new-group-container {:flex 1 @@ -33,6 +34,9 @@ :fontFamily font :color text1-color}) +(def group-name-validation-message + {:color :red}) + (def members-text {:marginTop 24 :marginBottom 16 diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index 37fac291a0..8ec3ba3fb7 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -92,7 +92,7 @@ :location-command-description "Send location" :phone-command-description "Send phone number" :phone-request-text "Phone number request" - :confirmation-code-coomand-description "Send confirmation code" + :confirmation-code-command-description "Send confirmation code" :confirmation-code-request-text "Confirmation code request" :send-command-description "Send location" :request-command-description "Send request"