Merge pull request #64 from status-im/commands-button

Commands button. Phone number validation. Drawer and Profile views.
This commit is contained in:
Jarrad 2016-05-11 14:42:12 +02:00
commit 3d8adf840e
45 changed files with 772 additions and 131 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

View File

@ -15,6 +15,7 @@
[syng-im.components.chats.new-group :refer [new-group]] [syng-im.components.chats.new-group :refer [new-group]]
[syng-im.components.chat.new-participants :refer [new-participants]] [syng-im.components.chat.new-participants :refer [new-participants]]
[syng-im.components.chat.remove-participants :refer [remove-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.logging :as log]
[syng-im.utils.utils :refer [toast]] [syng-im.utils.utils :refer [toast]]
[syng-im.navigation :as nav] [syng-im.navigation :as nav]
@ -29,7 +30,7 @@
;; this listener and handle application's closing ;; this listener and handle application's closing
;; in handlers ;; in handlers
(let [stack (subscribe [:navigation-stack])] (let [stack (subscribe [:navigation-stack])]
(when (< 1 (count stack)) (when (< 1 (count @stack))
(dispatch [:navigate-back]) (dispatch [:navigate-back])
true)))] true)))]
(add-event-listener "hardwareBackPress" new-listener))) (add-event-listener "hardwareBackPress" new-listener)))
@ -46,7 +47,8 @@
:chat-list [chats-list] :chat-list [chats-list]
:new-group [new-group] :new-group [new-group]
:contact-list [contact-list] :contact-list [contact-list]
:chat [chat])))) :chat [chat]
:profile [profile]))))
(defn init [] (defn init []
(dispatch-sync [:initialize-db]) (dispatch-sync [:initialize-db])

View File

@ -96,9 +96,9 @@
subtitle])]]]) subtitle])]]])
(defn actions-list-view [] (defn actions-list-view []
(let [{:keys [group-chat active]} (let [{:keys [group-chat chat-id]}
(subscribe [:chat-properties [:group-chat :name :contacts :active]])] (subscribe [:chat-properties [:group-chat :chat-id]])]
(when-let [actions (when (and @group-chat @active) (when-let [actions (if @group-chat
[{:title "Add Contact to chat" [{:title "Add Contact to chat"
:icon :menu_group :icon :menu_group
:icon-style {:width 25 :icon-style {:width 25
@ -123,7 +123,12 @@
:icon :settings :icon :settings
:icon-style {:width 20 :icon-style {:width 20
:height 13} :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-wrapper
[view st/actions-separator] [view st/actions-separator]
[view st/actions-view [view st/actions-view

View File

@ -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}]]])))))

View File

@ -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})

View File

@ -7,13 +7,8 @@
chat-background chat-background
color-black]])) color-black]]))
(def money-input (def command-input-and-suggestions-container
{:flex 1 {:flexDirection :column})
:marginLeft 8
:lineHeight 42
:fontSize 32
:fontFamily font
:color :black})
(def command-input-container (def command-input-container
{:flexDirection :row {:flexDirection :row

View File

@ -6,5 +6,4 @@
(defn money-input-view [command] (defn money-input-view [command]
[simple-command-input-view command [simple-command-input-view command
{:keyboardType :numeric {:keyboardType :numeric}])
:style st/money-input}])

View File

@ -1,7 +1,9 @@
(ns syng-im.components.chat.input.phone (ns syng-im.components.chat.input.phone
(:require (:require
[syng-im.components.chat.input.simple-command [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] (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?])

View File

@ -1,12 +1,12 @@
(ns syng-im.components.chat.input.simple-command (ns syng-im.components.chat.input.simple-command
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view [syng-im.components.react :refer [view
image
icon icon
text text
text-input text-input
touchable-highlight]] touchable-highlight]]
[syng-im.resources :as res] [syng-im.resources :as res]
[syng-im.components.chat.content-suggestions :refer [content-suggestions-view]]
[syng-im.components.chat.input.input-styles :as st])) [syng-im.components.chat.input.input-styles :as st]))
(defn cancel-command-input [] (defn cancel-command-input []
@ -19,23 +19,31 @@
(dispatch [:stage-command]) (dispatch [:stage-command])
(cancel-command-input)) (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])] (let [message-atom (subscribe [:get-chat-command-content])]
(fn [command input-options] (fn [command input-options & {:keys [validator]}]
(let [message @message-atom] (let [message @message-atom]
[view st/command-input-container [view st/command-input-and-suggestions-container
[view (st/command-text-container command) [content-suggestions-view]
[text {:style st/command-text} (:text command)]] [view st/command-input-container
[text-input (merge {:style st/command-input [view (st/command-text-container command)
:autoFocus true [text {:style st/command-text} (:text command)]]
:onChangeText set-input-message [text-input (merge {:style st/command-input
:onSubmitEditing send-command} :autoFocus true
input-options) :onChangeText set-input-message
message] :onSubmitEditing (fn []
(if (pos? (count message)) (when (valid? message validator)
[touchable-highlight {:on-press send-command} (send-command)))}
[view st/send-container [icon :send st/send-icon]]] input-options)
[touchable-highlight {:on-press cancel-command-input} message]
[view st/cancel-container (if (valid? message validator)
[image {:source res/icon-close-gray [touchable-highlight {:on-press send-command}
:style st/cancel-icon}]]])])))) [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]]])]]))))

View File

@ -18,24 +18,39 @@
(dispatch [:send-group-chat-msg chat-id input-message]) (dispatch [:send-group-chat-msg chat-id input-message])
(dispatch [:send-chat-msg])))) (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 [] (defn plain-message-input-view []
(let [chat (subscribe [:get-current-chat]) (let [chat (subscribe [:get-current-chat])
input-message-atom (subscribe [:get-chat-input-text]) 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 [] (fn []
(let [input-message @input-message-atom] (let [input-message @input-message-atom]
[view st/input-container [view st/input-container
[suggestions-view] [suggestions-view]
[view st/input-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 [text-input {:style st/message-input
:autoFocus (pos? (count @staged-commands-atom)) :autoFocus (pos? (count @staged-commands-atom))
:onChangeText set-input-message :onChangeText set-input-message
:onSubmitEditing #(send @chat input-message)} :onSubmitEditing #(try-send @chat @staged-commands-atom
input-message)}
input-message] input-message]
[icon :smile st/smile-icon] [icon :smile st/smile-icon]
(when (or (pos? (count input-message)) (when (message-valid? @staged-commands-atom input-message)
(pos? (count @staged-commands-atom)))
[touchable-highlight {:on-press #(send @chat input-message)} [touchable-highlight {:on-press #(send @chat input-message)}
[view st/send-container [view st/send-container
[icon :send st/send-icon]]])]])))) [icon :send st/send-icon]]])]]))))

View File

@ -12,17 +12,22 @@
:height 56 :height 56
:backgroundColor color-white}) :backgroundColor color-white})
(def switch-commands-touchable
{:width 56
:height 56
:alignItems :center
:justifyContent :center})
(def list-icon (def list-icon
{:marginTop 22 {:width 13
:marginRight 6 :height 12})
:marginBottom 6
:marginLeft 21 (def close-icon
:width 13 {:width 12
:height 12}) :height 12})
(def message-input (def message-input
{:flex 1 {:flex 1
:marginLeft 16
:marginTop -2 :marginTop -2
:padding 0 :padding 0
:fontSize 14 :fontSize 14

View File

@ -4,6 +4,7 @@
(:require [re-frame.core :refer [subscribe dispatch]] (:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view [syng-im.components.react :refer [view
text text
icon
touchable-highlight touchable-highlight
list-view list-view
list-item]] list-item]]
@ -16,12 +17,15 @@
(defn suggestion-list-item [suggestion] (defn suggestion-list-item [suggestion]
[touchable-highlight [touchable-highlight
{:onPress #(set-command-input (keyword (:command suggestion)))} {:onPress #(set-command-input (keyword (:command suggestion)))}
[view st/suggestion-item-container [view st/suggestion-container
[view (st/suggestion-background suggestion) [view st/suggestion-sub-container
[text {:style st/suggestion-text} [view (st/suggestion-background suggestion)
(:text suggestion)]] [text {:style st/suggestion-text}
[text {:style st/suggestion-description} (:text suggestion)]]
(:description suggestion)]]]) [text {:style st/value-text}
(:text suggestion)]
[text {:style st/description-text}
(:description suggestion)]]]])
(defn render-row [row _ _] (defn render-row [row _ _]
(list-item [suggestion-list-item (js->clj row :keywordize-keys true)])) (list-item [suggestion-list-item (js->clj row :keywordize-keys true)]))
@ -31,8 +35,14 @@
(fn [] (fn []
(let [suggestions @suggestions-atom] (let [suggestions @suggestions-atom]
(when (seq suggestions) (when (seq suggestions)
[view (st/suggestions-container suggestions) [view nil
[list-view {:dataSource (to-datasource suggestions) [touchable-highlight {:style st/drag-down-touchable
:enableEmptySections true :onPress (fn []
:renderRow render-row ;; TODO hide suggestions?
:style {}}]]))))) )}
[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}]]])))))

View File

@ -1,44 +1,69 @@
(ns syng-im.components.chat.suggestions-styles (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 (def suggestion-height 88)
{:flexDirection :row
:marginVertical 1 (def suggestion-container
:marginHorizontal 0 {:flexDirection :column
:height 40 :paddingLeft 16
:backgroundColor color-white}) :backgroundColor color-white})
(def suggestion-sub-container
{:height suggestion-height
:borderBottomWidth 1
:borderBottomColor separator-color})
(defn suggestion-background (defn suggestion-background
[{:keys [color]}] [{:keys [color]}]
{:flexDirection :column {:alignSelf :flex-start
:position :absolute :marginTop 10
:top 10 :height 24
:left 60
:backgroundColor color :backgroundColor color
:borderRadius 10}) :borderRadius 50})
(def suggestion-text (def suggestion-text
{:marginTop -2 {:marginTop 2.5
:marginHorizontal 10 :marginHorizontal 12
:fontSize 14 :fontSize 12
:fontFamily font :fontFamily font
:color color-white}) :color color-white})
(def suggestion-description (def value-text
{:flex 1 {:marginTop 6
:position :absolute
:top 7
:left 190
:lineHeight 18
:fontSize 14 :fontSize 14
:fontFamily font :fontFamily font
:color :black}) :color text1-color})
(defn suggestions-container (def description-text
[suggestions] {:marginTop 2
:fontSize 12
:fontFamily font
:color text2-color})
(defn suggestions-container [suggestions-count]
{:flexDirection :row {:flexDirection :row
:marginVertical 1 :marginVertical 1
:marginHorizontal 0 :marginHorizontal 0
:height (min 105 (* 42 (count suggestions))) :height (min 168 (* suggestion-height suggestions-count))
:backgroundColor color-white :backgroundColor color-white
:borderRadius 5}) :borderRadius 5})
(def drag-down-touchable
{:height 22
:alignItems :center
:justifyContent :center})
(def drag-down-icon
{:width 16
:height 16})

View File

@ -13,6 +13,7 @@
[syng-im.utils.listview :refer [to-realm-datasource]] [syng-im.utils.listview :refer [to-realm-datasource]]
[reagent.core :as r] [reagent.core :as r]
[syng-im.components.chats.chat-list-item :refer [chat-list-item]] [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 [syng-im.components.action-button :refer [action-button
action-button-item]] action-button-item]]
[syng-im.components.styles :refer [font [syng-im.components.styles :refer [font
@ -26,10 +27,10 @@
[syng-im.components.icons.ionicons :refer [icon]])) [syng-im.components.icons.ionicons :refer [icon]]))
(defn chats-list-toolbar [] (defn chats-list-toolbar []
[toolbar {:nav-action {:image {:source {:uri "icon_hamburger"} [toolbar {:nav-action {:image {:source {:uri "icon_hamburger"}
:style {:width 16 :style {:width 16
:height 12}} :height 12}}
:handler (fn [])} :handler open-drawer}
:title "Chats" :title "Chats"
:action {:image {:source {:uri "icon_search"} :action {:image {:source {:uri "icon_search"}
:style {:width 17 :style {:width 17
@ -42,28 +43,29 @@
(let [chats @chats (let [chats @chats
_ (log/debug "chats=" chats) _ (log/debug "chats=" chats)
datasource (to-realm-datasource chats)] datasource (to-realm-datasource chats)]
[view {:style {:flex 1 [drawer-view {:navigator navigator}
:backgroundColor "white"}} [view {:style {:flex 1
[chats-list-toolbar] :backgroundColor "white"}}
[list-view {:dataSource datasource [chats-list-toolbar]
[list-view {:dataSource datasource
:enableEmptySections true :enableEmptySections true
:renderRow (fn [row section-id row-id] :renderRow (fn [row section-id row-id]
(r/as-element [chat-list-item row navigator])) (r/as-element [chat-list-item row navigator]))
:style {:backgroundColor "white"}}] :style {:backgroundColor "white"}}]
[action-button {:buttonColor color-blue} [action-button {:buttonColor color-blue}
[action-button-item {:title "New Chat" [action-button-item {:title "New Chat"
:buttonColor "#9b59b6" :buttonColor "#9b59b6"
:onPress #(dispatch [:navigate-to :onPress #(dispatch [:navigate-to
:contact-list])} :contact-list])}
[icon {:name "android-create" [icon {:name "android-create"
:style {:fontSize 20 :style {:fontSize 20
:height 22 :height 22
:color "white"}}]] :color "white"}}]]
[action-button-item {:title "New Group Chat" [action-button-item {:title "New Group Chat"
:buttonColor "#1abc9c" :buttonColor "#1abc9c"
:onPress (fn [] :onPress (fn []
(dispatch [:show-group-new navigator]))} (dispatch [:show-group-new]))}
[icon {:name "person-stalker" [icon {:name "person-stalker"
:style {:fontSize 20 :style {:fontSize 20
:height 22 :height 22
:color "white"}}]]]])))) :color "white"}}]]]]]))))

View File

@ -1,11 +1,11 @@
(ns syng-im.components.contact-list.contact (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.resources :as res]
[syng-im.navigation :as nav]
[syng-im.components.contact-list.contact-inner :refer [contact-inner-view]])) [syng-im.components.contact-list.contact-inner :refer [contact-inner-view]]))
(defn show-chat [navigator whisper-identity] (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]}] (defn contact-view [{:keys [navigator contact]}]
(let [{:keys [whisper-identity]} contact] (let [{:keys [whisper-identity]} contact]

View File

@ -43,7 +43,7 @@
(fn [] (fn []
(let [contacts-ds (get-data-source @contacts)] (let [contacts-ds (get-data-source @contacts)]
[view {:style {:flex 1 [view {:style {:flex 1
:backgroundColor "white"}} :backgroundColor color-white}}
[contact-list-toolbar navigator] [contact-list-toolbar navigator]
(when contacts-ds (when contacts-ds
[list-view {:dataSource contacts-ds [list-view {:dataSource contacts-ds

View File

@ -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])

View File

@ -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})

View File

@ -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"]]]]])))

View File

@ -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})

View File

@ -26,6 +26,8 @@
:placeholder "Type"} :placeholder "Type"}
props) props)
text]) 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] (defn icon [n style]

View File

@ -5,6 +5,7 @@
(def title-font "sans-serif-medium") (def title-font "sans-serif-medium")
(def color-blue "#7099e6") (def color-blue "#7099e6")
(def color-blue-transparent "#7099e632")
(def color-black "#000000de") (def color-black "#000000de")
(def color-purple "#a187d5") (def color-purple "#a187d5")
(def color-gray "#838c93de") (def color-gray "#838c93de")
@ -16,8 +17,9 @@
(def text1-color color-black) (def text1-color color-black)
(def text2-color color-gray) (def text2-color color-gray)
(def text3-color color-blue)
(def online-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 chat-background color-light-gray)
(def selected-message-color "#E4E9ED") (def selected-message-color "#E4E9ED")
(def separator-color "#0000001f") (def separator-color "#0000001f")

View File

@ -15,8 +15,7 @@
toolbar-background1]] toolbar-background1]]
[syng-im.components.realm :refer [list-view]] [syng-im.components.realm :refer [list-view]]
[syng-im.utils.listview :refer [to-realm-datasource]] [syng-im.utils.listview :refer [to-realm-datasource]]
[reagent.core :as r] [reagent.core :as r]))
[syng-im.navigation :refer [nav-pop]]))
(defn toolbar [{:keys [navigator title nav-action action background-color content style]}] (defn toolbar [{:keys [navigator title nav-action action background-color content style]}]
(let [style (merge {:flexDirection "row" (let [style (merge {:flexDirection "row"
@ -31,7 +30,7 @@
:alignItems "center" :alignItems "center"
:justifyContent "center"} :justifyContent "center"}
[image (:image nav-action)]]] [image (:image nav-action)]]]
[touchable-highlight {:on-press #(nav-pop navigator)} [touchable-highlight {:on-press #(dispatch [:navigate-back])}
[view {:width 56 [view {:width 56
:height 56} :height 56}
[image {:source {:uri "icon_back"} [image {:source {:uri "icon_back"}

View File

@ -4,7 +4,7 @@
;; schema of app-db ;; schema of app-db
(def schema {:greeting s/Str}) (def schema {:greeting s/Str})
(def default-view :discovery) (def default-view :chat-list)
;; initial state of app-db ;; initial state of app-db
(def app-db {:greeting "Hello Clojure in iOS and Android!" (def app-db {:greeting "Hello Clojure in iOS and Android!"
@ -27,6 +27,7 @@
(def protocol-initialized-path [:protocol-initialized]) (def protocol-initialized-path [:protocol-initialized])
(def identity-password-path [:identity-password]) (def identity-password-path [:identity-password])
(def contact-identity-path [:contact-identity])
(def current-chat-id-path [:current-chat-id]) (def current-chat-id-path [:current-chat-id])
(def updated-chats-signal-path [:chats-updated-signal]) (def updated-chats-signal-path [:chats-updated-signal])
(defn updated-chat-signal-path [chat-id] (defn updated-chat-signal-path [chat-id]

View File

@ -2,6 +2,8 @@
(:require (:require
[re-frame.core :refer [register-handler after dispatch debug enrich]] [re-frame.core :refer [register-handler after dispatch debug enrich]]
[schema.core :as s :include-macros true] [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.db :as db :refer [app-db schema]]
[syng-im.protocol.api :refer [init-protocol]] [syng-im.protocol.api :refer [init-protocol]]
[syng-im.protocol.protocol-handler :refer [make-handler]] [syng-im.protocol.protocol-handler :refer [make-handler]]
@ -27,7 +29,8 @@
get-command-handler get-command-handler
load-commands load-commands
apply-staged-commands apply-staged-commands
check-suggestion]] check-suggestion
switch-command-suggestions]]
[syng-im.handlers.sign-up :as sign-up-service] [syng-im.handlers.sign-up :as sign-up-service]
[syng-im.components.discovery.handlers :as discovery] [syng-im.components.discovery.handlers :as discovery]
[syng-im.models.chats :refer [chat-exists? [syng-im.models.chats :refer [chat-exists?
@ -76,7 +79,9 @@
;; -- Common -------------------------------------------------------------- ;; -- Common --------------------------------------------------------------
(register-handler :initialize-db (register-handler :initialize-db
(fn [_ _] app-db)) (fn [_ _]
(assoc app-db
:signed-up (storage/get kv/kv-store :signed-up))))
(register-handler :set-loading (register-handler :set-loading
(fn [db [_ value]] (fn [db [_ value]]
@ -445,13 +450,23 @@
(fn [db [_ value]] (fn [db [_ value]]
(contacts/load-syng-contacts db))) (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 -------------------------------------------------------------- ;; -- Chats --------------------------------------------------------------
(register-handler :show-chat (register-handler :show-chat
(fn [db [action chat-id navigator nav-type]] (fn [db [action chat-id navigator nav-type]]
(log/debug action "chat-id" chat-id) (log/debug action "chat-id" chat-id)
(let [db (set-current-chat-id db chat-id)] (let [db (-> db
(dispatch [:navigate-to navigator {:view-id :chat} nav-type]) (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))) db)))
(register-handler :init-console-chat (register-handler :init-console-chat
@ -474,6 +489,10 @@
(register-handler :set-chat-input-text (register-handler :set-chat-input-text
((enrich update-command) update-text)) ((enrich update-command) update-text))
(register-handler :switch-command-suggestions
(fn [db [_]]
(switch-command-suggestions db)))
(register-handler :set-chat-command (register-handler :set-chat-command
(fn [db [_ command-key]] (fn [db [_ command-key]]
;; todo what is going on there?! ;; todo what is going on there?!
@ -552,9 +571,9 @@
db))) db)))
(register-handler :show-group-new (register-handler :show-group-new
(fn [db [action navigator]] (fn [db [action]]
(log/debug action) (log/debug action)
(nav-push navigator {:view-id :new-group}) (dispatch [:navigate-to :new-group])
(clear-new-group db))) (clear-new-group db)))
(register-handler :select-for-new-group (register-handler :select-for-new-group

View File

@ -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)))
[]))

View File

@ -1,7 +1,9 @@
(ns syng-im.handlers.suggestions (ns syng-im.handlers.suggestions
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.db :as db] [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 [syng-im.models.commands :refer [commands
suggestions suggestions
get-commands get-commands
@ -62,3 +64,10 @@
[suggestion] (filter #(= suggestion-text' (:text %)) [suggestion] (filter #(= suggestion-text' (:text %))
(get-commands db))] (get-commands db))]
suggestion))) 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 "!")))

View File

@ -43,6 +43,9 @@
(defn set-chat-input-text [db text] (defn set-chat-input-text [db text]
(assoc-in db (db/chat-input-text-path (current-chat-id 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 (comment
(swap! re-frame.db/app-db (fn [db] (swap! re-frame.db/app-db (fn [db]

View File

@ -1,6 +1,7 @@
(ns syng-im.models.contacts (ns syng-im.models.contacts
(:require [cljs.core.async :as async :refer [chan put! <! >!]] (:require [cljs.core.async :as async :refer [chan put! <! >!]]
[re-frame.core :refer [subscribe dispatch dispatch-sync]] [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.db :as db]
[syng-im.utils.utils :refer [log toast]] [syng-im.utils.utils :refer [log toast]]
[syng-im.persistence.realm :as realm] [syng-im.persistence.realm :as realm]
[syng-im.persistence.realm :as r] [syng-im.persistence.realm :as r]
@ -106,7 +107,20 @@
(r/sorted :name :asc)))) (r/sorted :name :asc))))
(defn contact-by-identity [identity] (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 (comment

View File

@ -11,13 +11,17 @@
[syng-im.models.messages :refer [get-messages]] [syng-im.models.messages :refer [get-messages]]
[syng-im.models.contacts :refer [contacts-list [syng-im.models.contacts :refer [contacts-list
contacts-list-exclude contacts-list-exclude
contacts-list-include]] contacts-list-include
contact-identity
contact-by-identity]]
[syng-im.models.commands :refer [get-commands [syng-im.models.commands :refer [get-commands
get-chat-command get-chat-command
get-chat-command-content get-chat-command-content
get-chat-command-request get-chat-command-request
parse-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 -------------------------------------------------------------- ;; -- Chat --------------------------------------------------------------
@ -38,6 +42,16 @@
(reaction))] (reaction))]
(reaction (get-suggestions @db @input-text))))) (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 (register-sub :get-commands
(fn [db _] (fn [db _]
(reaction (get-commands @db)))) (reaction (get-commands @db))))
@ -120,6 +134,11 @@
(reaction (reaction
(contacts-list)))) (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 (register-sub :all-new-contacts
(fn [db _] (fn [db _]
(let [current-chat-id (reaction (current-chat-id @db)) (let [current-chat-id (reaction (current-chat-id @db))

View File

@ -7,3 +7,9 @@
(defn format-phone-number [number] (defn format-phone-number [number]
(str (.getNumber (js/PhoneNumber. number country-code "international")))) (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)))))