diff --git a/android/app/src/main/res/drawable-hdpi/icon_close_gray.png b/android/app/src/main/res/drawable-hdpi/icon_close_gray.png new file mode 100644 index 0000000000..4342d2e4f0 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_close_gray.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_drag_down.png b/android/app/src/main/res/drawable-hdpi/icon_drag_down.png new file mode 100644 index 0000000000..7d0dfec989 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_drag_down.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_more_vertical_blue.png b/android/app/src/main/res/drawable-hdpi/icon_more_vertical_blue.png new file mode 100644 index 0000000000..f8bc4ec674 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_more_vertical_blue.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_close_gray.png b/android/app/src/main/res/drawable-mdpi/icon_close_gray.png new file mode 100644 index 0000000000..c0cca73335 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_close_gray.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_drag_down.png b/android/app/src/main/res/drawable-mdpi/icon_drag_down.png new file mode 100644 index 0000000000..88182afaf3 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_drag_down.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_more_vertical_blue.png b/android/app/src/main/res/drawable-mdpi/icon_more_vertical_blue.png new file mode 100644 index 0000000000..acbe47357d Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_more_vertical_blue.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_close_gray.png b/android/app/src/main/res/drawable-xhdpi/icon_close_gray.png new file mode 100644 index 0000000000..1eea738abe Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_close_gray.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_drag_down.png b/android/app/src/main/res/drawable-xhdpi/icon_drag_down.png new file mode 100644 index 0000000000..ca0f2b6f4e Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_drag_down.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_more_vertical_blue.png b/android/app/src/main/res/drawable-xhdpi/icon_more_vertical_blue.png new file mode 100644 index 0000000000..d58d42bf3c Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_more_vertical_blue.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_close_gray.png b/android/app/src/main/res/drawable-xxhdpi/icon_close_gray.png new file mode 100644 index 0000000000..c1f1c5f300 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_close_gray.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_drag_down.png b/android/app/src/main/res/drawable-xxhdpi/icon_drag_down.png new file mode 100644 index 0000000000..7e7a4912bc Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_drag_down.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_more_vertical_blue.png b/android/app/src/main/res/drawable-xxhdpi/icon_more_vertical_blue.png new file mode 100644 index 0000000000..79449d9dff Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_more_vertical_blue.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_close_gray.png b/android/app/src/main/res/drawable-xxxhdpi/icon_close_gray.png new file mode 100644 index 0000000000..07b1e5d3da Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_close_gray.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_drag_down.png b/android/app/src/main/res/drawable-xxxhdpi/icon_drag_down.png new file mode 100644 index 0000000000..0fe8e31411 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_drag_down.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_more_vertical_blue.png b/android/app/src/main/res/drawable-xxxhdpi/icon_more_vertical_blue.png new file mode 100644 index 0000000000..460e8081e4 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_more_vertical_blue.png differ diff --git a/src/syng_im/android/core.cljs b/src/syng_im/android/core.cljs index b9207063bf..a2ecc6c3d5 100644 --- a/src/syng_im/android/core.cljs +++ b/src/syng_im/android/core.cljs @@ -15,6 +15,7 @@ [syng-im.components.chats.new-group :refer [new-group]] [syng-im.components.chat.new-participants :refer [new-participants]] [syng-im.components.chat.remove-participants :refer [remove-participants]] + [syng-im.components.profile :refer [profile]] [syng-im.utils.logging :as log] [syng-im.utils.utils :refer [toast]] [syng-im.navigation :as nav] @@ -29,7 +30,7 @@ ;; this listener and handle application's closing ;; in handlers (let [stack (subscribe [:navigation-stack])] - (when (< 1 (count stack)) + (when (< 1 (count @stack)) (dispatch [:navigate-back]) true)))] (add-event-listener "hardwareBackPress" new-listener))) @@ -46,7 +47,8 @@ :chat-list [chats-list] :new-group [new-group] :contact-list [contact-list] - :chat [chat])))) + :chat [chat] + :profile [profile])))) (defn init [] (dispatch-sync [:initialize-db]) diff --git a/src/syng_im/components/chat.cljs b/src/syng_im/components/chat.cljs index 4c443093d7..1644bc8b6f 100644 --- a/src/syng_im/components/chat.cljs +++ b/src/syng_im/components/chat.cljs @@ -96,9 +96,9 @@ subtitle])]]]) (defn actions-list-view [] - (let [{:keys [group-chat active]} - (subscribe [:chat-properties [:group-chat :name :contacts :active]])] - (when-let [actions (when (and @group-chat @active) + (let [{:keys [group-chat chat-id]} + (subscribe [:chat-properties [:group-chat :chat-id]])] + (when-let [actions (if @group-chat [{:title "Add Contact to chat" :icon :menu_group :icon-style {:width 25 @@ -123,7 +123,12 @@ :icon :settings :icon-style {:width 20 :height 13} - :handler (fn [])}])] + :handler (fn [])}] + [{:title "Profile" + :icon :menu_group + :icon-style {:width 25 + :height 19} + :handler #(dispatch [:show-profile @chat-id])}])] [view st/actions-wrapper [view st/actions-separator] [view st/actions-view diff --git a/src/syng_im/components/chat/content_suggestions.cljs b/src/syng_im/components/chat/content_suggestions.cljs new file mode 100644 index 0000000000..e79a88bc6f --- /dev/null +++ b/src/syng_im/components/chat/content_suggestions.cljs @@ -0,0 +1,50 @@ +(ns syng-im.components.chat.content-suggestions + (:require-macros + [natal-shell.core :refer [with-error-view]]) + (:require [clojure.string :as cstr] + [reagent.core :as r] + [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.components.react :refer [view + icon + text + touchable-highlight + list-view + list-item]] + [syng-im.components.chat.content-suggestions-styles :as st] + [syng-im.utils.listview :refer [to-datasource]] + [syng-im.utils.utils :refer [log toast http-post]] + [syng-im.utils.logging :as log])) + +(defn set-command-content [content] + (dispatch [:set-chat-command-content content])) + +(defn suggestion-list-item [suggestion] + [touchable-highlight {:onPress (fn [] + (set-command-content (:value suggestion))) + :underlay-color :transparent} + [view st/suggestion-container + [view st/suggestion-sub-container + [text {:style st/value-text} + (:value suggestion)] + [text {:style st/description-text} + (:description suggestion)]]]]) + +(defn render-row [row section-id row-id] + (list-item [suggestion-list-item (js->clj row :keywordize-keys true)])) + +(defn content-suggestions-view [] + (let [suggestions-atom (subscribe [:get-content-suggestions])] + (fn [] + (let [suggestions @suggestions-atom] + (when (seq suggestions) + [view nil + [touchable-highlight {:style st/drag-down-touchable + :onPress (fn [] + ;; TODO hide suggestions? + ) + :underlay-color :transparent} + [view nil + [icon :drag_down st/drag-down-icon]]] + [view (st/suggestions-container (count suggestions)) + [list-view {:dataSource (to-datasource suggestions) + :renderRow render-row}]]]))))) diff --git a/src/syng_im/components/chat/content_suggestions_styles.cljs b/src/syng_im/components/chat/content_suggestions_styles.cljs new file mode 100644 index 0000000000..c0169e8ea3 --- /dev/null +++ b/src/syng_im/components/chat/content_suggestions_styles.cljs @@ -0,0 +1,54 @@ +(ns syng-im.components.chat.content-suggestions-styles + (:require [syng-im.components.styles :refer [font + 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 suggestion-height 56) + +(def suggestion-container + {:flexDirection :column + :paddingLeft 16 + :backgroundColor color-white}) + +(def suggestion-sub-container + {:height suggestion-height + :borderBottomWidth 1 + :borderBottomColor separator-color}) + +(def value-text + {:marginTop 9 + :fontSize 14 + :fontFamily font + :color text1-color}) + +(def description-text + {:marginTop 1.5 + :fontSize 14 + :fontFamily font + :color text2-color}) + +(defn suggestions-container [suggestions-count] + {:flexDirection :row + :marginVertical 1 + :marginHorizontal 0 + :height (min 150 (* suggestion-height suggestions-count)) + :backgroundColor color-white + :borderRadius 5}) + +(def drag-down-touchable + {:height 22 + :alignItems :center + :justifyContent :center}) + +(def drag-down-icon + {:width 16 + :height 16}) diff --git a/src/syng_im/components/chat/input/input_styles.cljs b/src/syng_im/components/chat/input/input_styles.cljs index c5debbcafd..7079f4d101 100644 --- a/src/syng_im/components/chat/input/input_styles.cljs +++ b/src/syng_im/components/chat/input/input_styles.cljs @@ -7,13 +7,8 @@ chat-background color-black]])) -(def money-input - {:flex 1 - :marginLeft 8 - :lineHeight 42 - :fontSize 32 - :fontFamily font - :color :black}) +(def command-input-and-suggestions-container + {:flexDirection :column}) (def command-input-container {:flexDirection :row diff --git a/src/syng_im/components/chat/input/money.cljs b/src/syng_im/components/chat/input/money.cljs index be53c5dbc2..ac85a9fc9e 100644 --- a/src/syng_im/components/chat/input/money.cljs +++ b/src/syng_im/components/chat/input/money.cljs @@ -6,5 +6,4 @@ (defn money-input-view [command] [simple-command-input-view command - {:keyboardType :numeric - :style st/money-input}]) + {:keyboardType :numeric}]) diff --git a/src/syng_im/components/chat/input/phone.cljs b/src/syng_im/components/chat/input/phone.cljs index 267178e382..2ff0812b4a 100644 --- a/src/syng_im/components/chat/input/phone.cljs +++ b/src/syng_im/components/chat/input/phone.cljs @@ -1,7 +1,9 @@ (ns syng-im.components.chat.input.phone (:require [syng-im.components.chat.input.simple-command - :refer [simple-command-input-view]])) + :refer [simple-command-input-view]] + [syng-im.utils.phone-number :refer [valid-mobile-number?]])) (defn phone-input-view [command] - [simple-command-input-view command {:keyboardType :phone-pad}]) + [simple-command-input-view command {:keyboardType :phone-pad} + :validator valid-mobile-number?]) diff --git a/src/syng_im/components/chat/input/simple_command.cljs b/src/syng_im/components/chat/input/simple_command.cljs index 53982f762a..31292f8df5 100644 --- a/src/syng_im/components/chat/input/simple_command.cljs +++ b/src/syng_im/components/chat/input/simple_command.cljs @@ -1,12 +1,12 @@ (ns syng-im.components.chat.input.simple-command (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] [syng-im.components.react :refer [view - image icon text text-input touchable-highlight]] [syng-im.resources :as res] + [syng-im.components.chat.content-suggestions :refer [content-suggestions-view]] [syng-im.components.chat.input.input-styles :as st])) (defn cancel-command-input [] @@ -19,23 +19,31 @@ (dispatch [:stage-command]) (cancel-command-input)) -(defn simple-command-input-view [command input-options] +(defn valid? [message validator] + (if validator + (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] + (fn [command input-options & {:keys [validator]}] (let [message @message-atom] - [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 send-command} - input-options) - message] - (if (pos? (count message)) - [touchable-highlight {:on-press send-command} - [view st/send-container [icon :send st/send-icon]]] - [touchable-highlight {:on-press cancel-command-input} - [view st/cancel-container - [image {:source res/icon-close-gray - :style st/cancel-icon}]]])])))) + [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)))} + input-options) + message] + (if (valid? message validator) + [touchable-highlight {:on-press send-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]]])]])))) diff --git a/src/syng_im/components/chat/plain_message_input.cljs b/src/syng_im/components/chat/plain_message_input.cljs index f90fa057cc..7931c21b85 100644 --- a/src/syng_im/components/chat/plain_message_input.cljs +++ b/src/syng_im/components/chat/plain_message_input.cljs @@ -18,24 +18,39 @@ (dispatch [:send-group-chat-msg chat-id input-message]) (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])] + 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 - [icon :list st/list-icon] + [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 #(send @chat input-message)} + :onSubmitEditing #(try-send @chat @staged-commands-atom + input-message)} input-message] [icon :smile st/smile-icon] - (when (or (pos? (count input-message)) - (pos? (count @staged-commands-atom))) + (when (message-valid? @staged-commands-atom input-message) [touchable-highlight {:on-press #(send @chat input-message)} [view st/send-container [icon :send st/send-icon]]])]])))) diff --git a/src/syng_im/components/chat/plain_message_input_styles.cljs b/src/syng_im/components/chat/plain_message_input_styles.cljs index 01b90f2bf8..8e31264837 100644 --- a/src/syng_im/components/chat/plain_message_input_styles.cljs +++ b/src/syng_im/components/chat/plain_message_input_styles.cljs @@ -12,17 +12,22 @@ :height 56 :backgroundColor color-white}) +(def switch-commands-touchable + {:width 56 + :height 56 + :alignItems :center + :justifyContent :center}) + (def list-icon - {:marginTop 22 - :marginRight 6 - :marginBottom 6 - :marginLeft 21 - :width 13 - :height 12}) + {:width 13 + :height 12}) + +(def close-icon + {:width 12 + :height 12}) (def message-input {:flex 1 - :marginLeft 16 :marginTop -2 :padding 0 :fontSize 14 diff --git a/src/syng_im/components/chat/suggestions.cljs b/src/syng_im/components/chat/suggestions.cljs index b1bbd0d75e..cfef517fbe 100644 --- a/src/syng_im/components/chat/suggestions.cljs +++ b/src/syng_im/components/chat/suggestions.cljs @@ -4,6 +4,7 @@ (:require [re-frame.core :refer [subscribe dispatch]] [syng-im.components.react :refer [view text + icon touchable-highlight list-view list-item]] @@ -16,12 +17,15 @@ (defn suggestion-list-item [suggestion] [touchable-highlight {:onPress #(set-command-input (keyword (:command suggestion)))} - [view st/suggestion-item-container - [view (st/suggestion-background suggestion) - [text {:style st/suggestion-text} - (:text suggestion)]] - [text {:style st/suggestion-description} - (:description suggestion)]]]) + [view st/suggestion-container + [view st/suggestion-sub-container + [view (st/suggestion-background suggestion) + [text {:style st/suggestion-text} + (:text suggestion)]] + [text {:style st/value-text} + (:text suggestion)] + [text {:style st/description-text} + (:description suggestion)]]]]) (defn render-row [row _ _] (list-item [suggestion-list-item (js->clj row :keywordize-keys true)])) @@ -31,8 +35,14 @@ (fn [] (let [suggestions @suggestions-atom] (when (seq suggestions) - [view (st/suggestions-container suggestions) - [list-view {:dataSource (to-datasource suggestions) - :enableEmptySections true - :renderRow render-row - :style {}}]]))))) + [view nil + [touchable-highlight {:style st/drag-down-touchable + :onPress (fn [] + ;; TODO hide suggestions? + )} + [view nil + [icon :drag_down st/drag-down-icon]]] + [view (st/suggestions-container (count suggestions)) + [list-view {:dataSource (to-datasource suggestions) + :enableEmptySections true + :renderRow render-row}]]]))))) diff --git a/src/syng_im/components/chat/suggestions_styles.cljs b/src/syng_im/components/chat/suggestions_styles.cljs index 80ae964e68..1e3a1fb352 100644 --- a/src/syng_im/components/chat/suggestions_styles.cljs +++ b/src/syng_im/components/chat/suggestions_styles.cljs @@ -1,44 +1,69 @@ (ns syng-im.components.chat.suggestions-styles - (:require [syng-im.components.styles :refer [font color-white]])) + (:require [syng-im.components.styles :refer [font + 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 suggestion-item-container - {:flexDirection :row - :marginVertical 1 - :marginHorizontal 0 - :height 40 - :backgroundColor color-white}) +(def suggestion-height 88) + +(def suggestion-container + {:flexDirection :column + :paddingLeft 16 + :backgroundColor color-white}) + +(def suggestion-sub-container + {:height suggestion-height + :borderBottomWidth 1 + :borderBottomColor separator-color}) (defn suggestion-background [{:keys [color]}] - {:flexDirection :column - :position :absolute - :top 10 - :left 60 + {:alignSelf :flex-start + :marginTop 10 + :height 24 :backgroundColor color - :borderRadius 10}) + :borderRadius 50}) (def suggestion-text - {:marginTop -2 - :marginHorizontal 10 - :fontSize 14 + {:marginTop 2.5 + :marginHorizontal 12 + :fontSize 12 :fontFamily font :color color-white}) -(def suggestion-description - {:flex 1 - :position :absolute - :top 7 - :left 190 - :lineHeight 18 +(def value-text + {:marginTop 6 :fontSize 14 :fontFamily font - :color :black}) + :color text1-color}) -(defn suggestions-container - [suggestions] +(def description-text + {:marginTop 2 + :fontSize 12 + :fontFamily font + :color text2-color}) + +(defn suggestions-container [suggestions-count] {:flexDirection :row :marginVertical 1 :marginHorizontal 0 - :height (min 105 (* 42 (count suggestions))) + :height (min 168 (* suggestion-height suggestions-count)) :backgroundColor color-white :borderRadius 5}) + +(def drag-down-touchable + {:height 22 + :alignItems :center + :justifyContent :center}) + +(def drag-down-icon + {:width 16 + :height 16}) diff --git a/src/syng_im/components/chats/chats_list.cljs b/src/syng_im/components/chats/chats_list.cljs index f102e7be6b..2b63d8d76f 100644 --- a/src/syng_im/components/chats/chats_list.cljs +++ b/src/syng_im/components/chats/chats_list.cljs @@ -13,6 +13,7 @@ [syng-im.utils.listview :refer [to-realm-datasource]] [reagent.core :as r] [syng-im.components.chats.chat-list-item :refer [chat-list-item]] + [syng-im.components.drawer :refer [drawer-view open-drawer]] [syng-im.components.action-button :refer [action-button action-button-item]] [syng-im.components.styles :refer [font @@ -26,10 +27,10 @@ [syng-im.components.icons.ionicons :refer [icon]])) (defn chats-list-toolbar [] - [toolbar {:nav-action {:image {:source {:uri "icon_hamburger"} - :style {:width 16 - :height 12}} - :handler (fn [])} + [toolbar {:nav-action {:image {:source {:uri "icon_hamburger"} + :style {:width 16 + :height 12}} + :handler open-drawer} :title "Chats" :action {:image {:source {:uri "icon_search"} :style {:width 17 @@ -42,28 +43,29 @@ (let [chats @chats _ (log/debug "chats=" chats) datasource (to-realm-datasource chats)] - [view {:style {:flex 1 - :backgroundColor "white"}} - [chats-list-toolbar] - [list-view {:dataSource datasource + [drawer-view {:navigator navigator} + [view {:style {:flex 1 + :backgroundColor "white"}} + [chats-list-toolbar] + [list-view {:dataSource datasource :enableEmptySections true - :renderRow (fn [row section-id row-id] - (r/as-element [chat-list-item row navigator])) - :style {:backgroundColor "white"}}] - [action-button {:buttonColor color-blue} - [action-button-item {:title "New Chat" - :buttonColor "#9b59b6" - :onPress #(dispatch [:navigate-to - :contact-list])} - [icon {:name "android-create" - :style {:fontSize 20 - :height 22 - :color "white"}}]] - [action-button-item {:title "New Group Chat" - :buttonColor "#1abc9c" - :onPress (fn [] - (dispatch [:show-group-new navigator]))} - [icon {:name "person-stalker" - :style {:fontSize 20 - :height 22 - :color "white"}}]]]])))) + :renderRow (fn [row section-id row-id] + (r/as-element [chat-list-item row navigator])) + :style {:backgroundColor "white"}}] + [action-button {:buttonColor color-blue} + [action-button-item {:title "New Chat" + :buttonColor "#9b59b6" + :onPress #(dispatch [:navigate-to + :contact-list])} + [icon {:name "android-create" + :style {:fontSize 20 + :height 22 + :color "white"}}]] + [action-button-item {:title "New Group Chat" + :buttonColor "#1abc9c" + :onPress (fn [] + (dispatch [:show-group-new]))} + [icon {:name "person-stalker" + :style {:fontSize 20 + :height 22 + :color "white"}}]]]]])))) diff --git a/src/syng_im/components/contact_list/contact.cljs b/src/syng_im/components/contact_list/contact.cljs index 7b9d5af1c1..52bce8b6a7 100644 --- a/src/syng_im/components/contact_list/contact.cljs +++ b/src/syng_im/components/contact_list/contact.cljs @@ -1,11 +1,11 @@ (ns syng-im.components.contact-list.contact - (:require [syng-im.components.react :refer [view text image touchable-highlight]] + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.components.react :refer [view text image touchable-highlight]] [syng-im.resources :as res] - [syng-im.navigation :as nav] [syng-im.components.contact-list.contact-inner :refer [contact-inner-view]])) (defn show-chat [navigator whisper-identity] - (nav/nav-push navigator {:view-id :chat})) + (dispatch [:show-chat whisper-identity navigator :push])) (defn contact-view [{:keys [navigator contact]}] (let [{:keys [whisper-identity]} contact] diff --git a/src/syng_im/components/contact_list/contact_list.cljs b/src/syng_im/components/contact_list/contact_list.cljs index d4c3ef242c..70d5145a9e 100644 --- a/src/syng_im/components/contact_list/contact_list.cljs +++ b/src/syng_im/components/contact_list/contact_list.cljs @@ -43,7 +43,7 @@ (fn [] (let [contacts-ds (get-data-source @contacts)] [view {:style {:flex 1 - :backgroundColor "white"}} + :backgroundColor color-white}} [contact-list-toolbar navigator] (when contacts-ds [list-view {:dataSource contacts-ds diff --git a/src/syng_im/components/drawer.cljs b/src/syng_im/components/drawer.cljs new file mode 100644 index 0000000000..c1f7b807f2 --- /dev/null +++ b/src/syng_im/components/drawer.cljs @@ -0,0 +1,79 @@ +(ns syng-im.components.drawer + (:require [clojure.string :as s] + [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [reagent.core :as r] + [syng-im.components.react :refer [android? + view + text + image + navigator + toolbar-android + drawer-layout-android + touchable-opacity]] + [syng-im.resources :as res] + [syng-im.components.drawer-styles :as st])) + +(defonce drawer-atom (atom)) + +(defn open-drawer [] + (.openDrawer @drawer-atom)) + +(defn close-drawer [] + (.closeDrawer @drawer-atom)) + +(defn user-photo [{:keys [photo-path]}] + [image {:source (if (s/blank? photo-path) + res/user-no-photo + {:uri photo-path}) + :style st/user-photo}]) + +(defn menu-item [{:keys [name handler]}] + [touchable-opacity {:style st/menu-item-touchable + :onPress (fn [] + (close-drawer) + (handler))} + [text {:style st/menu-item-text} + name]]) + +(defn drawer-menu [navigator] + [view st/drawer-menu + [view st/user-photo-container + [user-photo {}]] + [view st/name-container + [text {:style st/name-text} + "Status"]] + [view st/menu-items-container + [menu-item {:name "Profile" + :handler (fn [] + (dispatch [:show-profile navigator]))}] + [menu-item {:name "Settings" + :handler (fn [] + ;; TODO not implemented + )}] + [menu-item {:name "Discovery" + :handler (fn [] + (dispatch [:navigate-to :discovery]))}] + [menu-item {:name "Contacts" + :handler (fn [] + (dispatch [:show-contacts navigator]))}] + [menu-item {:name "Invite friends" + :handler (fn [] + ;; TODO not implemented + )}] + [menu-item {:name "FAQ" + :handler (fn [])}]] + [view st/switch-users-container + [touchable-opacity {:onPress (fn [] + (close-drawer) + ;; TODO not implemented + )} + [text {:style st/switch-users-text} + "Switch users"]]]]) + +(defn drawer-view [{:keys [navigator]} items] + [drawer-layout-android {:drawerWidth 300 + :drawerPosition js/React.DrawerLayoutAndroid.positions.Left + :render-navigation-view #(r/as-element [drawer-menu navigator]) + :ref (fn [drawer] + (reset! drawer-atom drawer))} + items]) diff --git a/src/syng_im/components/drawer_styles.cljs b/src/syng_im/components/drawer_styles.cljs new file mode 100644 index 0000000000..b6ee3c0808 --- /dev/null +++ b/src/syng_im/components/drawer_styles.cljs @@ -0,0 +1,64 @@ +(ns syng-im.components.drawer-styles + (:require [syng-im.components.styles :refer [font + 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 user-photo + {:borderRadius 50 + :width 64 + :height 64}) + +(def menu-item-touchable + {:height 48 + :paddingLeft 16 + :paddingTop 14}) + +(def menu-item-text + {:fontSize 14 + :fontFamily font + :lineHeight 21 + :color text1-color}) + +(def drawer-menu + {:flex 1 + :backgroundColor color-white + :flexDirection :column}) + +(def user-photo-container + {:marginTop 40 + :alignItems :center + :justifyContent :center}) + +(def name-container + {:marginTop 20 + :alignItems :center}) + +(def name-text + {:marginTop -2.5 + :color text1-color + :fontSize 16}) + +(def menu-items-container + {:flex 1 + :marginTop 80 + :alignItems :stretch + :flexDirection :column}) + +(def switch-users-container + {:paddingVertical 36 + :alignItems :center}) + +(def switch-users-text + {:fontSize 14 + :fontFamily font + :lineHeight 21 + :color text3-color}) diff --git a/src/syng_im/components/profile.cljs b/src/syng_im/components/profile.cljs new file mode 100644 index 0000000000..7ce6bea22d --- /dev/null +++ b/src/syng_im/components/profile.cljs @@ -0,0 +1,76 @@ +(ns syng-im.components.profile + (:require [clojure.string :as s] + [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.components.react :refer [android? + view + text + text-input + image + icon + scroll-view + touchable-highlight + touchable-opacity]] + [syng-im.resources :as res] + [syng-im.components.profile-styles :as st])) + +(defn user-photo [{:keys [photo-path]}] + [image {:source (if (s/blank? photo-path) + res/user-no-photo + {:uri photo-path}) + :style st/user-photo}]) + +(defn user-online [{:keys [online]}] + (when online + [view st/user-online-container + [view st/user-online-dot-left] + [view st/user-online-dot-right]])) + +(defn profile-property-view [{:keys [name value]}] + [view st/profile-property-view-container + [view st/profile-property-view-sub-container + [text {:style st/profile-property-view-label} + name] + [text {:style st/profile-property-view-value} + value]]]) + +(defn message-user [identity] + (when identity + (dispatch [:show-chat identity nil :push]))) + +(defn profile [] + (let [contact (subscribe [:contact])] + (fn [] + [scroll-view {:style st/profile} + [touchable-highlight {:style st/profile-back-button-touchable + :on-press #(dispatch [:navigate-back])} + [view st/profile-back-button-container + [icon :back st/profile-back-button-icon]]] + [view st/status-block + [view st/user-photo-container + [user-photo {}] + [user-online {:online true}]] + [text {:style st/user-name} + (:name @contact)] + [text {:style st/status} + "!not implemented"] + [view st/btns-container + [touchable-highlight {:onPress #(message-user (:whisper-identity @contact))} + [view st/message-btn + [text {:style st/message-btn-text} + "Message"]]] + [touchable-highlight {:onPress (fn [] + ;; TODO not implemented + )} + [view st/more-btn + [icon :more_vertical_blue st/more-btn-image]]]]] + [view st/profile-properties-container + [profile-property-view {:name "Username" + :value (:name @contact)}] + [profile-property-view {:name "Phone number" + :value (:phone-number @contact)}] + [profile-property-view {:name "Email" + :value "!not implemented"}] + [view st/report-user-container + [touchable-opacity {} + [text {:style st/report-user-text} + "REPORT USER"]]]]]))) diff --git a/src/syng_im/components/profile_styles.cljs b/src/syng_im/components/profile_styles.cljs new file mode 100644 index 0000000000..11c58ccf04 --- /dev/null +++ b/src/syng_im/components/profile_styles.cljs @@ -0,0 +1,154 @@ +(ns syng-im.components.profile-styles + (:require [syng-im.components.styles :refer [font + color-light-blue-transparent + color-white + color-black + color-blue + color-blue-transparent + selected-message-color + online-color + separator-color + text1-color + text2-color]])) + +(def user-photo + {:borderRadius 50 + :width 64 + :height 64}) + +(def user-online-container + {:position :absolute + :top 44 + :left 44 + :width 24 + :height 24 + :borderRadius 50 + :backgroundColor online-color + :borderWidth 2 + :borderColor color-white}) + +(def user-online-dot + {:position :absolute + :top 8 + :left 5 + :width 4 + :height 4 + :borderRadius 50 + :backgroundColor color-white}) + +(def user-online-dot-left + (assoc user-online-dot :left 5)) + +(def user-online-dot-right + (assoc user-online-dot :left 11)) + +(def profile-property-view-container + {:height 85 + :paddingHorizontal 16}) + +(def profile-property-view-sub-container + {:borderBottomWidth 1 + :borderBottomColor separator-color}) + +(def profile-property-view-label + {:marginTop 16 + :fontSize 14 + :fontFamily font + :color text2-color}) + +(def profile-property-view-value + {:marginTop 11 + :height 40 + :fontSize 16 + :fontFamily font + :color text1-color}) + +(def profile + {:flex 1 + :backgroundColor color-white + :flexDirection :column}) + +(def profile-back-button-touchable + {:position :absolute}) + +(def profile-back-button-container + {:width 56 + :height 56}) + +(def profile-back-button-icon + {:marginTop 21 + :marginLeft 23 + :width 8 + :height 14}) + +(def status-block + {:alignSelf :center + :alignItems :center + :width 249}) + +(def user-photo-container + {:marginTop 26}) + +(def user-name + {:marginTop 20 + :fontSize 18 + :fontFamily font + :color text1-color}) + +(def status + {:marginTop 10 + :fontFamily font + :fontSize 14 + :lineHeight 20 + :textAlign :center + :color text2-color}) + +(def btns-container + {:marginTop 18 + :flexDirection :row}) + +(def message-btn + {:height 40 + :justifyContent :center + :backgroundColor color-blue + :paddingLeft 25 + :paddingRight 25 + :borderRadius 50}) + +(def message-btn-text + {:marginTop -2.5 + :fontSize 14 + :fontFamily font + :color color-white}) + +(def more-btn + {:marginLeft 10 + :width 40 + :height 40 + :alignItems :center + :justifyContent :center + :backgroundColor color-blue-transparent + :padding 8 + :borderRadius 50}) + +(def more-btn-image + {:width 4 + :height 16}) + +(def profile-properties-container + {:marginTop 20 + :alignItems :stretch + :flexDirection :column}) + +(def report-user-container + {:marginTop 50 + :marginBottom 43 + :alignItems :center}) + +(def report-user-text + {:fontSize 14 + :fontFamily font + :lineHeight 21 + :color text2-color + ;; IOS: + :letterSpacing 0.5}) diff --git a/src/syng_im/components/react.cljs b/src/syng_im/components/react.cljs index 64b6dcd001..1ce9a93718 100644 --- a/src/syng_im/components/react.cljs +++ b/src/syng_im/components/react.cljs @@ -26,6 +26,8 @@ :placeholder "Type"} props) text]) +(def drawer-layout-android (r/adapt-react-class (.-DrawerLayoutAndroid js/React))) +(def touchable-opacity (r/adapt-react-class (.-TouchableOpacity js/React))) (defn icon [n style] diff --git a/src/syng_im/components/styles.cljs b/src/syng_im/components/styles.cljs index 5f55404a35..1f02586a7f 100644 --- a/src/syng_im/components/styles.cljs +++ b/src/syng_im/components/styles.cljs @@ -5,6 +5,7 @@ (def title-font "sans-serif-medium") (def color-blue "#7099e6") +(def color-blue-transparent "#7099e632") (def color-black "#000000de") (def color-purple "#a187d5") (def color-gray "#838c93de") @@ -16,8 +17,9 @@ (def text1-color color-black) (def text2-color color-gray) +(def text3-color color-blue) (def online-color color-blue) -(def new-messages-count-color "#7099e632") +(def new-messages-count-color color-blue-transparent) (def chat-background color-light-gray) (def selected-message-color "#E4E9ED") (def separator-color "#0000001f") diff --git a/src/syng_im/components/toolbar.cljs b/src/syng_im/components/toolbar.cljs index 4e9a38fd0c..2b5608f078 100644 --- a/src/syng_im/components/toolbar.cljs +++ b/src/syng_im/components/toolbar.cljs @@ -15,8 +15,7 @@ toolbar-background1]] [syng-im.components.realm :refer [list-view]] [syng-im.utils.listview :refer [to-realm-datasource]] - [reagent.core :as r] - [syng-im.navigation :refer [nav-pop]])) + [reagent.core :as r])) (defn toolbar [{:keys [navigator title nav-action action background-color content style]}] (let [style (merge {:flexDirection "row" @@ -31,7 +30,7 @@ :alignItems "center" :justifyContent "center"} [image (:image nav-action)]]] - [touchable-highlight {:on-press #(nav-pop navigator)} + [touchable-highlight {:on-press #(dispatch [:navigate-back])} [view {:width 56 :height 56} [image {:source {:uri "icon_back"} diff --git a/src/syng_im/db.cljs b/src/syng_im/db.cljs index 09f45f2725..54280b7d11 100644 --- a/src/syng_im/db.cljs +++ b/src/syng_im/db.cljs @@ -4,7 +4,7 @@ ;; schema of app-db (def schema {:greeting s/Str}) -(def default-view :discovery) +(def default-view :chat-list) ;; initial state of app-db (def app-db {:greeting "Hello Clojure in iOS and Android!" @@ -27,6 +27,7 @@ (def protocol-initialized-path [:protocol-initialized]) (def identity-password-path [:identity-password]) +(def contact-identity-path [:contact-identity]) (def current-chat-id-path [:current-chat-id]) (def updated-chats-signal-path [:chats-updated-signal]) (defn updated-chat-signal-path [chat-id] diff --git a/src/syng_im/handlers.cljs b/src/syng_im/handlers.cljs index 05decc143a..063c6f67dd 100644 --- a/src/syng_im/handlers.cljs +++ b/src/syng_im/handlers.cljs @@ -2,6 +2,8 @@ (:require [re-frame.core :refer [register-handler after dispatch debug enrich]] [schema.core :as s :include-macros true] + [syng-im.persistence.simple-kv-store :as kv] + [syng-im.protocol.state.storage :as storage] [syng-im.db :as db :refer [app-db schema]] [syng-im.protocol.api :refer [init-protocol]] [syng-im.protocol.protocol-handler :refer [make-handler]] @@ -27,7 +29,8 @@ get-command-handler load-commands apply-staged-commands - check-suggestion]] + check-suggestion + switch-command-suggestions]] [syng-im.handlers.sign-up :as sign-up-service] [syng-im.components.discovery.handlers :as discovery] [syng-im.models.chats :refer [chat-exists? @@ -76,7 +79,9 @@ ;; -- Common -------------------------------------------------------------- (register-handler :initialize-db - (fn [_ _] app-db)) + (fn [_ _] + (assoc app-db + :signed-up (storage/get kv/kv-store :signed-up)))) (register-handler :set-loading (fn [db [_ value]] @@ -445,13 +450,23 @@ (fn [db [_ value]] (contacts/load-syng-contacts db))) +(register-handler :show-profile + (fn [db [action identity]] + (log/debug action) + (let [db (contacts/set-contact-identity db identity)] + (dispatch [:navigate-to :profile]) + db))) + ;; -- Chats -------------------------------------------------------------- (register-handler :show-chat (fn [db [action chat-id navigator nav-type]] (log/debug action "chat-id" chat-id) - (let [db (set-current-chat-id db chat-id)] - (dispatch [:navigate-to navigator {:view-id :chat} nav-type]) + (let [db (-> db + (create-chat chat-id [chat-id] false) + (set-current-chat-id chat-id))] + ;; (dispatch [:navigate-to navigator {:view-id :chat} nav-type]) + (dispatch [:navigate-to :chat]) db))) (register-handler :init-console-chat @@ -474,6 +489,10 @@ (register-handler :set-chat-input-text ((enrich update-command) update-text)) +(register-handler :switch-command-suggestions + (fn [db [_]] + (switch-command-suggestions db))) + (register-handler :set-chat-command (fn [db [_ command-key]] ;; todo what is going on there?! @@ -552,9 +571,9 @@ db))) (register-handler :show-group-new - (fn [db [action navigator]] + (fn [db [action]] (log/debug action) - (nav-push navigator {:view-id :new-group}) + (dispatch [:navigate-to :new-group]) (clear-new-group db))) (register-handler :select-for-new-group diff --git a/src/syng_im/handlers/content_suggestions.cljs b/src/syng_im/handlers/content_suggestions.cljs new file mode 100644 index 0000000000..8906558ff2 --- /dev/null +++ b/src/syng_im/handlers/content_suggestions.cljs @@ -0,0 +1,22 @@ +(ns syng-im.handlers.content-suggestions + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.db :as db] + [syng-im.utils.logging :as log] + [clojure.string :as s])) + +(def suggestions + {:phone [{:value "89171111111" + :description "Number format 1"} + {:value "+79171111111" + :description "Number format 2"} + {:value "9171111111" + :description "Number format 3"}]}) + +(defn get-content-suggestions [db 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))) + command-suggestions))) + [])) diff --git a/src/syng_im/handlers/suggestions.cljs b/src/syng_im/handlers/suggestions.cljs index 8ce337b436..4b227940bb 100644 --- a/src/syng_im/handlers/suggestions.cljs +++ b/src/syng_im/handlers/suggestions.cljs @@ -1,7 +1,9 @@ (ns syng-im.handlers.suggestions (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] [syng-im.db :as db] - [syng-im.models.chat :refer [current-chat-id]] + [syng-im.models.chat :refer [current-chat-id + set-chat-input-text + get-chat-input-text]] [syng-im.models.commands :refer [commands suggestions get-commands @@ -62,3 +64,10 @@ [suggestion] (filter #(= suggestion-text' (:text %)) (get-commands db))] suggestion))) + +(defn typing-command? [db] + (let [text (get-chat-input-text db)] + (suggestion? text))) + +(defn switch-command-suggestions [db] + (set-chat-input-text db (if (typing-command? db) nil "!"))) diff --git a/src/syng_im/models/chat.cljs b/src/syng_im/models/chat.cljs index 920442174b..763be721db 100644 --- a/src/syng_im/models/chat.cljs +++ b/src/syng_im/models/chat.cljs @@ -43,6 +43,9 @@ (defn set-chat-input-text [db text] (assoc-in db (db/chat-input-text-path (current-chat-id db)) text)) +(defn get-chat-input-text [db] + (get-in db (db/chat-input-text-path (current-chat-id db)))) + (comment (swap! re-frame.db/app-db (fn [db] diff --git a/src/syng_im/models/contacts.cljs b/src/syng_im/models/contacts.cljs index 53acb69cab..3468cd7eab 100644 --- a/src/syng_im/models/contacts.cljs +++ b/src/syng_im/models/contacts.cljs @@ -1,6 +1,7 @@ (ns syng-im.models.contacts (:require [cljs.core.async :as async :refer [chan put! !]] [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.db :as db] [syng-im.utils.utils :refer [log toast]] [syng-im.persistence.realm :as realm] [syng-im.persistence.realm :as r] @@ -106,7 +107,20 @@ (r/sorted :name :asc)))) (defn contact-by-identity [identity] - (r/single-cljs (r/get-by-field :contacts :whisper-identity identity))) + (if (= identity "console") + {:phone-number "" + :whisper-identity "console" + :name "Console" + :photo-path ""} + (r/single-cljs (r/get-by-field :contacts :whisper-identity identity)))) + +;;;;;;;;;;;;;;;;;;;;---------------------------------------------- + +(defn set-contact-identity [db contact-id] + (assoc-in db db/contact-identity-path contact-id)) + +(defn contact-identity [db] + (get-in db db/contact-identity-path)) (comment diff --git a/src/syng_im/subs.cljs b/src/syng_im/subs.cljs index 465a4a52bd..695001b450 100644 --- a/src/syng_im/subs.cljs +++ b/src/syng_im/subs.cljs @@ -11,13 +11,17 @@ [syng-im.models.messages :refer [get-messages]] [syng-im.models.contacts :refer [contacts-list contacts-list-exclude - contacts-list-include]] + contacts-list-include + contact-identity + contact-by-identity]] [syng-im.models.commands :refer [get-commands get-chat-command get-chat-command-content get-chat-command-request parse-command-request]] - [syng-im.handlers.suggestions :refer [get-suggestions]])) + [syng-im.handlers.suggestions :refer [get-suggestions + typing-command?]] + [syng-im.handlers.content-suggestions :refer [get-content-suggestions]])) ;; -- Chat -------------------------------------------------------------- @@ -38,6 +42,16 @@ (reaction))] (reaction (get-suggestions @db @input-text))))) +(register-sub :typing-command? + (fn [db _] + (reaction (typing-command? @db)))) + +(register-sub :get-content-suggestions + (fn [db _] + (let [command (reaction (get-chat-command @db)) + text (reaction (get-chat-command-content @db))] + (reaction (get-content-suggestions @db @command @text))))) + (register-sub :get-commands (fn [db _] (reaction (get-commands @db)))) @@ -120,6 +134,11 @@ (reaction (contacts-list)))) +(register-sub :contact + (fn [db _] + (let [identity (reaction (get-in @db db/contact-identity-path))] + (reaction (contact-by-identity @identity))))) + (register-sub :all-new-contacts (fn [db _] (let [current-chat-id (reaction (current-chat-id @db)) diff --git a/src/syng_im/utils/phone_number.cljs b/src/syng_im/utils/phone_number.cljs index 6a7481faf4..0d5a960193 100644 --- a/src/syng_im/utils/phone_number.cljs +++ b/src/syng_im/utils/phone_number.cljs @@ -7,3 +7,9 @@ (defn format-phone-number [number] (str (.getNumber (js/PhoneNumber. number country-code "international")))) + +(defn valid-mobile-number? [number] + (when (string? number) + (let [number-obj (js/PhoneNumber. number country-code "international")] + (and (.isValid number-obj) + (.isMobile number-obj)))))