diff --git a/.re-natal b/.re-natal index 4fecaf5ca5..a547f09695 100644 --- a/.re-natal +++ b/.re-natal @@ -18,7 +18,8 @@ "react-native-linear-gradient", "react-native-android-sms-listener", "react-native-camera", - "react-native-qrcode" + "react-native-qrcode", + "identicon.js" ], "imageDirs": [ "images" diff --git a/android/app/src/main/res/drawable-hdpi/icon_group_big.png b/android/app/src/main/res/drawable-hdpi/icon_group_big.png new file mode 100644 index 0000000000..68687a6dfa Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_group_big.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_group_big.png b/android/app/src/main/res/drawable-mdpi/icon_group_big.png new file mode 100644 index 0000000000..c4bf8a7989 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_group_big.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_group_big.png b/android/app/src/main/res/drawable-xhdpi/icon_group_big.png new file mode 100644 index 0000000000..8b47926213 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_group_big.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_group_big.png b/android/app/src/main/res/drawable-xxhdpi/icon_group_big.png new file mode 100644 index 0000000000..a1bff88bb0 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_group_big.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_group_big.png b/android/app/src/main/res/drawable-xxxhdpi/icon_group_big.png new file mode 100644 index 0000000000..bf47ac4f7b Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_group_big.png differ diff --git a/package.json b/package.json index 877b612afe..b8c7a1a2a8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ }, "dependencies": { "awesome-phonenumber": "^1.0.13", + "identicon.js": "github:status-im/identicon.js", "react": "^0.14.5", "react-native": "^0.24.1", "react-native-action-button": "^1.1.4", diff --git a/src/status_im/android/core.cljs b/src/status_im/android/core.cljs index 154c7104ce..52c7cee85e 100644 --- a/src/status_im/android/core.cljs +++ b/src/status_im/android/core.cljs @@ -7,7 +7,7 @@ [status-im.subs] [status-im.components.react :refer [navigator app-registry]] [status-im.components.main-tabs :refer [main-tabs]] - [status-im.contacts.screen :refer [contact-list]] + [status-im.contacts.views.contact-list :refer [contact-list] ] [status-im.contacts.views.new-contact :refer [new-contact]] [status-im.qr-scanner.screen :refer [qr-scanner]] [status-im.discovery.screen :refer [discovery]] @@ -46,6 +46,7 @@ :new-group [new-group] :group-settings [group-settings] :contact-list [main-tabs] + :group-contacts [contact-list] :new-contact [new-contact] :qr-scanner [qr-scanner] :chat [chat] diff --git a/src/status_im/chats_list/screen.cljs b/src/status_im/chats_list/screen.cljs index 572f0ac5a9..9e38f48307 100644 --- a/src/status_im/chats_list/screen.cljs +++ b/src/status_im/chats_list/screen.cljs @@ -1,8 +1,10 @@ (ns status-im.chats-list.screen + (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [list-view list-item view + animated-view text image touchable-highlight]] @@ -11,42 +13,68 @@ [status-im.chats-list.views.chat-list-item :refer [chat-list-item]] [status-im.components.action-button :refer [action-button action-button-item]] - [status-im.components.drawer.view :refer [drawer-view open-drawer]] + [status-im.components.drawer.view :refer [open-drawer]] [status-im.components.styles :refer [color-blue + toolbar-background1 toolbar-background2]] [status-im.components.toolbar :refer [toolbar]] [status-im.components.icons.ionicons :refer [icon]] [status-im.i18n :refer [label]] - [status-im.chats-list.styles :as st])) + [status-im.chats-list.styles :as st] + [status-im.components.tabs.styles :refer [tabs-height]])) -(defn chats-list-toolbar [] +(defview chats-list-toolbar [] + [chats-scrolled? [:get :chats-scrolled?]] [toolbar {:nav-action {:image {:source {:uri :icon_hamburger} :style st/hamburger-icon} :handler open-drawer} :title (label :t/chats) - :background-color toolbar-background2 + :background-color (if chats-scrolled? + toolbar-background1 + toolbar-background2) ;; TODO implement search :action {:image {:source {:uri :icon_search} :style st/search-icon} :handler (fn [])}}]) (defn chats-list [] - (let [chats (subscribe [:get :chats])] + (let [chats (subscribe [:get :chats]) + chats-scrolled? (subscribe [:get :chats-scrolled?]) + animation? (subscribe [:animations :tabs-bar-animation?]) + tabs-bar-value (subscribe [:animations :tabs-bar-value]) + container-height (r/atom 0) + content-height (r/atom 0)] + (dispatch [:set :chats-scrolled? false]) (fn [] - [drawer-view - [view st/chats-container - [chats-list-toolbar] - [list-view {:dataSource (to-datasource @chats) - :renderRow (fn [row _ _] - (list-item [chat-list-item row])) - :style st/list-container}] + [view st/chats-container + [chats-list-toolbar] + [list-view {:dataSource (to-datasource @chats) + :renderRow (fn [row _ _] + (list-item [chat-list-item row])) + :style st/list-container + ;;; if "maximazing" chat list will make scroll to 0, + ;;; then disable maximazing + :onLayout (fn [event] + (when-not @chats-scrolled? + (let [height (.. event -nativeEvent -layout -height)] + (reset! container-height height)))) + :onContentSizeChange (fn [width height] + (reset! content-height height)) + :onScroll (fn [e] + (let [offset (.. e -nativeEvent -contentOffset -y) + min-content-height (+ @container-height tabs-height) + scrolled? (and (< 0 offset) (< min-content-height @content-height))] + (dispatch [:set :chats-scrolled? scrolled?]) + (dispatch [:set-animation :tabs-bar-animation? true])))}] + [animated-view {:style (st/action-buttons-container @animation? (or @tabs-bar-value 0)) + :pointerEvents :box-none} [action-button {:buttonColor color-blue :offsetY 16 :offsetX 16} [action-button-item {:title (label :t/new-chat) :buttonColor :#9b59b6 - :onPress #(dispatch [:navigate-to :contact-list])} + :onPress #(dispatch [:show-group-contacts :people])} [icon {:name :android-create :style st/create-icon}]] [action-button-item diff --git a/src/status_im/chats_list/styles.cljs b/src/status_im/chats_list/styles.cljs index ca72f89042..6621aedfce 100644 --- a/src/status_im/chats_list/styles.cljs +++ b/src/status_im/chats_list/styles.cljs @@ -6,7 +6,8 @@ online-color text1-color text2-color - new-messages-count-color]])) + new-messages-count-color]] + [status-im.components.tabs.styles :refer [tabs-height]])) (def chat-container {:flexDirection :row @@ -113,3 +114,11 @@ {:fontSize 20 :height 22 :color :white}) + +(defn action-buttons-container [animation? offset-y] + {:position :absolute + :left 0 + :right 0 + :top 0 + :bottom 0 + :transform [{:translateY (if animation? offset-y 1)}]}) diff --git a/src/status_im/components/chat_icon/screen.cljs b/src/status_im/components/chat_icon/screen.cljs index 0d93e38cbb..3b6437c193 100644 --- a/src/status_im/components/chat_icon/screen.cljs +++ b/src/status_im/components/chat_icon/screen.cljs @@ -6,7 +6,7 @@ image icon]] [status-im.components.chat-icon.styles :as st] - [status-im.components.styles :refer [color-purple]] + [status-im.components.styles :refer [default-chat-color]] [clojure.string :as s])) (defn default-chat-icon [name styles] @@ -63,6 +63,26 @@ :default-chat-icon (st/default-chat-icon-menu-item color) :default-chat-icon-text st/default-chat-icon-text}]) +(defn contact-icon-view [contact styles] + (let [photo-path (:photo-path contact) + ;; TODO stub data + online true] + [view (:container styles) + (if-not (s/blank? photo-path) + [chat-icon photo-path styles] + [default-chat-icon (:name contact) styles]) + [contact-online online styles]])) + +(defn contact-icon-contacts-tab [contact] + [contact-icon-view contact + {:container st/container-chat-list + :online-view st/online-view + :online-dot-left st/online-dot-left + :online-dot-right st/online-dot-right + :chat-icon st/chat-icon-chat-list + :default-chat-icon (st/default-chat-icon-chat-list default-chat-color) + :default-chat-icon-text st/default-chat-icon-text}]) + (defn profile-icon-view [photo-path name color online] (let [styles {:container st/container-profile :online-view st/online-view-profile @@ -81,7 +101,7 @@ [contact [:contact]] (let [;; TODO stub data online true - color color-purple] + color default-chat-color] [profile-icon-view (:photo-path contact) (:name contact) color online])) (defview my-profile-icon [] @@ -89,5 +109,5 @@ photo-path [:get :photo-path]] (let [;; TODO stub data online true - color color-purple] + color default-chat-color] [profile-icon-view photo-path name color online])) diff --git a/src/status_im/components/main_tabs.cljs b/src/status_im/components/main_tabs.cljs index 01551e75da..d59b1d676b 100644 --- a/src/status_im/components/main_tabs.cljs +++ b/src/status_im/components/main_tabs.cljs @@ -10,6 +10,7 @@ image touchable-highlight get-dimensions]] + [status-im.components.drawer.view :refer [drawer-view]] [status-im.components.animation :as anim] [status-im.chats-list.screen :refer [chats-list]] [status-im.discovery.screen :refer [discovery]] @@ -101,9 +102,10 @@ (defview main-tabs [] [view-id [:get :view-id] tab-animation? [:get :prev-tab-view-id]] - [view {:style common-st/flex} - [view {:style common-st/flex - :pointerEvents (if tab-animation? :none :auto)} - (doall (map #(tab-view %) tab-list))] - [tabs {:selected-view-id view-id - :tab-list tab-list}]]) + [drawer-view + [view {:style common-st/flex} + [view {:style common-st/flex + :pointerEvents (if tab-animation? :none :auto)} + (doall (map #(tab-view %) tab-list))] + [tabs {:selected-view-id view-id + :tab-list tab-list}]]]) diff --git a/src/status_im/components/styles.cljs b/src/status_im/components/styles.cljs index 0498011c07..22f6705300 100644 --- a/src/status_im/components/styles.cljs +++ b/src/status_im/components/styles.cljs @@ -10,6 +10,7 @@ (def color-black "#000000de") (def color-purple "#a187d5") (def color-gray "#838c93de") +(def color-gray2 "#8f838c93") (def color-white :white) (def color-light-blue "#bbc4cb") (def color-light-blue-transparent "#bbc4cb32") @@ -20,6 +21,7 @@ (def text2-color color-gray) (def text3-color color-blue) (def text4-color color-white) +(def text5-color "#838c938f") (def online-color color-blue) (def new-messages-count-color color-blue-transparent) (def chat-background color-light-gray) @@ -100,4 +102,4 @@ (def button-input {:flex 1 - :flexDirection :column}) \ No newline at end of file + :flexDirection :column}) diff --git a/src/status_im/components/tabs/styles.cljs b/src/status_im/components/tabs/styles.cljs index 45af60e439..2d873e88cd 100644 --- a/src/status_im/components/tabs/styles.cljs +++ b/src/status_im/components/tabs/styles.cljs @@ -10,21 +10,21 @@ text2-color toolbar-background1]])) +(def tabs-height 59) (def tab-height 56) -(def tabs - {:flex 1 - :position :absolute - :bottom 0 - :right 0 - :left 0 - }) +(defn tabs-container [hidden? animation? offset-y] + {:height tabs-height + :backgroundColor color-white + :marginBottom (if (or hidden? animation?) + (- tabs-height) 0) + :transform [{:translateY (if animation? offset-y 1)}]}) (def top-gradient {:flexDirection :row :height 3}) -(def tabs-container +(def tabs-inner-container {:flexDirection :row :height tab-height :opacity 1 @@ -55,10 +55,9 @@ :alignItems :center}) (defn tab-view-container [offset-x] - {:flex 1 - :position :absolute + {:position :absolute :top 0 :left 0 :right 0 - :bottom tab-height + :bottom 0 :transform [{:translateX offset-x}]}) diff --git a/src/status_im/components/tabs/tabs.cljs b/src/status_im/components/tabs/tabs.cljs index bd12614f3f..a6d10d5d8d 100644 --- a/src/status_im/components/tabs/tabs.cljs +++ b/src/status_im/components/tabs/tabs.cljs @@ -2,6 +2,7 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] [status-im.components.react :refer [view + animated-view text-input text image @@ -9,7 +10,8 @@ linear-gradient]] [reagent.core :as r] [status-im.components.tabs.styles :as st] - [status-im.components.tabs.tab :refer [tab]])) + [status-im.components.tabs.tab :refer [tab]] + [status-im.components.animation :as anim])) (defn create-tab [index data selected-view-id] (let [data (merge data {:key index @@ -17,10 +19,43 @@ :selected-view-id selected-view-id})] [tab data])) -(defview tabs [{:keys [style tab-list selected-view-id]}] - (let [style (merge st/tabs style)] - [view {:style style} - [linear-gradient {:colors ["rgba(24, 52, 76, 0.01)" "rgba(24, 52, 76, 0.085)" "rgba(24, 52, 76, 0.165)"] - :style st/top-gradient}] - [view st/tabs-container - (doall (map-indexed #(create-tab %1 %2 selected-view-id) tab-list))]])) +(defn animation-logic [{:keys [hidden? val]}] + (let [was-hidden? (atom (not @hidden?))] + (fn [_] + (when (not= @was-hidden? @hidden?) + (let [to-value (if @hidden? 0 (- st/tabs-height))] + (swap! was-hidden? not) + (anim/start + (anim/timing val {:toValue to-value + :duration 300}) + (fn [e] + ;; if to-value was changed, then new animation has started + (when (= to-value (if @hidden? 0 (- st/tabs-height))) + (dispatch [:set-animation :tabs-bar-animation? false]))))))))) + +(defn tabs-container [& children] + (let [chats-scrolled? (subscribe [:get :chats-scrolled?]) + animation? (subscribe [:animations :tabs-bar-animation?]) + tabs-bar-value (subscribe [:animations :tabs-bar-value]) + context {:hidden? chats-scrolled? + :val @tabs-bar-value} + on-update (animation-logic context)] + (anim/set-value @tabs-bar-value 0) + (r/create-class + {:component-did-mount + on-update + :component-did-update + on-update + :reagent-render + (fn [& children] + @chats-scrolled? + (into [animated-view {:style (st/tabs-container @chats-scrolled? @animation? @tabs-bar-value) + :pointerEvents (if @chats-scrolled? :none :auto)}] + children))}))) + +(defn tabs [{:keys [tab-list selected-view-id]}] + [tabs-container + [linear-gradient {:colors ["rgba(24, 52, 76, 0.01)" "rgba(24, 52, 76, 0.085)" "rgba(24, 52, 76, 0.165)"] + :style st/top-gradient}] + [view st/tabs-inner-container + (doall (map-indexed #(create-tab %1 %2 selected-view-id) tab-list))]]) diff --git a/src/status_im/contacts/screen.cljs b/src/status_im/contacts/screen.cljs index a763c2ea50..97342f9f01 100644 --- a/src/status_im/contacts/screen.cljs +++ b/src/status_im/contacts/screen.cljs @@ -1,17 +1,19 @@ (ns status-im.contacts.screen (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [reagent.core :as r] [status-im.components.react :refer [view text image touchable-highlight + linear-gradient + scroll-view list-view - list-item]] + list-item] :as react] [status-im.components.action-button :refer [action-button action-button-item]] - [status-im.contacts.views.contact :refer [contact-view]] - [status-im.components.styles :refer [toolbar-background2]] + [status-im.contacts.views.contact :refer [contact-extended-view]] [status-im.components.toolbar :refer [toolbar]] - [status-im.components.drawer.view :refer [drawer-view open-drawer]] + [status-im.components.drawer.view :refer [open-drawer]] [status-im.components.icons.ionicons :refer [icon]] [status-im.components.styles :refer [color-blue hamburger-icon @@ -19,41 +21,79 @@ create-icon toolbar-background2]] [status-im.contacts.styles :as st] - [status-im.utils.listview :as lw] [status-im.i18n :refer [label]])) -(defn render-row [row _ _] - (list-item [contact-view row])) - (defn contact-list-toolbar [] - [toolbar {:nav-action {:image {:source {:uri :icon_hamburger} - :style hamburger-icon} - :handler open-drawer} + [toolbar {:nav-action {:image {:source {:uri :icon_hamburger} + :style hamburger-icon} + :handler open-drawer} :title (label :t/contacts) :background-color toolbar-background2 + :style {:elevation 0} :action {:image {:source {:uri :icon_search} :style icon-search} :handler (fn [])}}]) -(defview contact-list [] - [contacts [:get-contacts]] - [drawer-view - [view st/contacts-list-container - [contact-list-toolbar] - ;; todo what if there is no contacts, should we show some information - ;; about this? - (when contacts - [list-view {:dataSource (lw/to-datasource contacts) - :enableEmptySections true - :renderRow render-row - :style st/contacts-list}]) - [action-button {:buttonColor color-blue - :offsetY 16 - :offsetX 16} - [action-button-item - {:title (label :t/new-contact) - :buttonColor :#9b59b6 - :onPress #(dispatch [:navigate-to :new-contact])} - [icon {:name :android-create - :style create-icon}]] - ]]]) +(def contacts-limit 10) + +(defn contact-group [contacts contacts-count title group top?] + [view st/contact-group + [view st/contact-group-header + (when-not top? + [linear-gradient {:style st/contact-group-header-gradient-top + :colors st/contact-group-header-gradient-top-colors}]) + [view st/contact-group-header-inner + [text {:style st/contact-group-text} title] + [text {:style st/contact-group-size-text} (str contacts-count)]] + [linear-gradient {:style st/contact-group-header-gradient-bottom + :colors st/contact-group-header-gradient-bottom-colors}]] + ;; todo what if there is no contacts, should we show some information + ;; about this? + [view {:flexDirection :column} + (for [contact contacts] + ;; TODO not imlemented: contact more button handler + ^{:key contact} [contact-extended-view contact nil nil])] + (when (= contacts-limit (count contacts)) + [view st/show-all + [touchable-highlight {:on-press #(dispatch [:show-group-contacts group])} + [text {:style st/show-all-text} (label :show-all)]]])]) + +(defn contact-list [] + (let [contacts (subscribe [:get-contacts-with-limit contacts-limit]) + contcats-count (subscribe [:contacts-count]) + show-toolbar-shadow? (r/atom false)] + (fn [] + [view st/contacts-list-container + [contact-list-toolbar] + [view {:style st/toolbar-shadow} + (when @show-toolbar-shadow? + [linear-gradient {:style st/contact-group-header-gradient-bottom + :colors st/contact-group-header-gradient-bottom-colors}])] + (if (pos? @contcats-count) + [scroll-view {:style st/contact-groups + :onScroll (fn [e] + (let [offset (.. e -nativeEvent -contentOffset -y)] + (reset! show-toolbar-shadow? (<= st/contact-group-header-height offset))))} + ;; TODO not implemented: dapps and persons separation + [contact-group + @contacts + @contcats-count + (label :t/contacs-group-dapps) + :dapps true] + [contact-group + @contacts + @contcats-count + (label :t/contacs-group-people) + :people false]] + [view st/empty-contact-groups + [react/icon :group_big st/empty-contacts-icon] + [text {:style st/empty-contacts-text} (label :t/no-contacts)]]) + [action-button {:buttonColor color-blue + :offsetY 16 + :offsetX 16} + [action-button-item + {:title (label :t/new-contact) + :buttonColor :#9b59b6 + :onPress #(dispatch [:navigate-to :new-contact])} + [icon {:name :android-create + :style create-icon}]]]]))) diff --git a/src/status_im/contacts/styles.cljs b/src/status_im/contacts/styles.cljs index 410adf7ed2..7e9ed688c6 100644 --- a/src/status_im/contacts/styles.cljs +++ b/src/status_im/contacts/styles.cljs @@ -1,72 +1,158 @@ (ns status-im.contacts.styles (:require [status-im.components.styles :refer [font + font-medium title-font text1-color + text2-color + text3-color + text5-color color-white toolbar-background2 - online-color]])) - - + online-color + color-gray2]])) (def contacts-list-container {:flex 1 :backgroundColor :white}) +(def toolbar-shadow + {:height 2 + :backgroundColor toolbar-background2}) + +(def contact-groups + {:flex 1 + :backgroundColor toolbar-background2}) + +(def empty-contact-groups + (merge contact-groups + {:align-items :center + :padding-top 150})) + +(def empty-contacts-icon + {:height 62 + :width 62}) + +(def empty-contacts-text + {:margin-top 12 + :font-size 16 + :color color-gray2}) + (def contacts-list {:backgroundColor :white}) -(def contact-photo-container - {:borderRadius 50}) +(def contact-group + {:flexDirection :column}) -(def photo-image - {:borderRadius 50 - :width 40 - :height 40}) +(def contact-group-header + {:flexDirection :column + :backgroundColor toolbar-background2}) -(def online-container - {:position :absolute - :top 24 - :left 24 - :width 20 - :height 20 - :borderRadius 50 - :backgroundColor online-color - :borderWidth 2 - :borderColor color-white}) +(def contact-group-header-inner + {:flexDirection :row + :alignItems :center + :height 48 + :backgroundColor toolbar-background2}) -(def online-dot - {:position :absolute - :top 6 - :width 4 - :height 4 - :borderRadius 50 +(def contact-group-text + {:flex 1 + :marginLeft 16 + :fontSize 14 + :fontFamily font-medium + :color text5-color}) + +(def contact-group-size-text + {:marginRight 14 + :fontSize 12 + :fontFamily font + :color text2-color}) + +(def contact-group-header-gradient-top + {:flexDirection :row + :height 3 + :backgroundColor toolbar-background2}) + +(def contact-group-header-gradient-top-colors + ["rgba(24, 52, 76, 0.165)" + "rgba(24, 52, 76, 0.03)" + "rgba(24, 52, 76, 0.01)"]) + +(def contact-group-header-gradient-bottom + {:flexDirection :row + :height 2 + :backgroundColor toolbar-background2}) + +(def contact-group-header-gradient-bottom-colors + ["rgba(24, 52, 76, 0.01)" + "rgba(24, 52, 76, 0.05)"]) + +(def contact-group-header-height (+ (:height contact-group-header-inner) + (:height contact-group-header-gradient-bottom))) + +(def show-all + {:flexDirection :row + :alignItems :center + :height 56 :backgroundColor color-white}) -(def online-dot-left - (assoc online-dot :left 3)) - -(def online-dot-right - (assoc online-dot :left 9)) +(def show-all-text + {:marginLeft 72 + :fontSize 14 + :fontFamily font-medium + :color text3-color + ;; ios only: + :letterSpacing 0.5}) (def contact-container - {:flexDirection :row - :height 56}) + {:flexDirection :row + :backgroundColor color-white}) -(def photo-container - {:marginTop 8 - :marginLeft 16 - :width 44 - :height 44}) +(def letter-container + {:paddingTop 11 + :paddingLeft 20 + :width 56}) -(def name-container - {:justifyContent :center}) +(def letter-text + {:fontSize 24 + :fontFamily font + :color text3-color}) + +(def contact-photo-container + {:marginTop 4 + :marginLeft 12}) + +(def contact-inner-container + {:flex 1 + :flexDirection :row + :height 56 + :backgroundColor color-white}) + +(def info-container + {:flex 1 + :flexDirection :column + :marginLeft 12 + :justifyContent :center}) (def name-text - {:marginLeft 16 - :fontSize 16 + {:fontSize 16 :fontFamily font :color text1-color}) +(def info-text + {:marginTop 1 + :fontSize 12 + :fontFamily font + :color text2-color}) + +(def more-btn + {:width 56 + :height 56 + :alignItems :center + :justifyContent :center}) + +(def more-btn-icon + {:width 4 + :height 16}) + ; new contact (def contact-form-container @@ -92,4 +178,4 @@ (def address-explication {:textAlign :center - :color "#838c93de"}) \ No newline at end of file + :color "#838c93de"}) diff --git a/src/status_im/contacts/subs.cljs b/src/status_im/contacts/subs.cljs index 76803f74dd..969518e1dd 100644 --- a/src/status_im/contacts/subs.cljs +++ b/src/status_im/contacts/subs.cljs @@ -1,28 +1,60 @@ (ns status-im.contacts.subs (:require-macros [reagent.ratom :refer [reaction]]) - (:require [re-frame.core :refer [register-sub]])) + (:require [re-frame.core :refer [register-sub subscribe]])) (register-sub :get-contacts (fn [db _] (let [contacts (reaction (:contacts @db))] (reaction (vals @contacts))))) +(defn sort-contacts [contacts] + (sort-by :name #(compare (clojure.string/lower-case %1) + (clojure.string/lower-case %2)) (vals contacts))) + (register-sub :all-contacts (fn [db _] (let [contacts (reaction (:contacts @db))] - (reaction (sort-by :name (vals @contacts)))))) + (reaction (sort-contacts @contacts))))) + +(register-sub :get-contacts-with-limit + (fn [_ [_ limit]] + (let [contacts (subscribe [:all-contacts])] + (reaction (take limit @contacts))))) + +(register-sub :contacts-count + (fn [_ _] + (let [contacts (subscribe [:all-contacts])] + (reaction (count @contacts))))) + +(defn get-contact-letter [contact] + (when-let [letter (first (:name contact))] + (clojure.string/upper-case letter))) + +(register-sub :contacts-with-letters + (fn [db _] + (let [contacts (reaction (:contacts @db))] + (reaction + (let [ordered (sort-contacts @contacts)] + (reduce (fn [prev cur] + (let [prev-letter (get-contact-letter (last prev)) + cur-letter (get-contact-letter cur)] + (conj prev + (if (not= prev-letter cur-letter) + (assoc cur :letter cur-letter) + cur)))) + [] ordered)))))) (defn contacts-by-chat [fn db chat-id] (let [chat (reaction (get-in @db [:chats chat-id])) contacts (reaction (:contacts @db))] (reaction - (when @chat - (let [current-participants (->> @chat - :contacts - (map :identity) - set)] - (fn #(current-participants (:whisper-identity %)) - (vals @contacts))))))) + (when @chat + (let [current-participants (->> @chat + :contacts + (map :identity) + set)] + (fn #(current-participants (:whisper-identity %)) + (vals @contacts))))))) (defn contacts-by-current-chat [fn db] (let [current-chat-id (:current-chat-id @db)] @@ -33,6 +65,10 @@ (let [identity (:contact-identity @db)] (reaction (get-in @db [:contacts identity]))))) +(register-sub :contact-by-identity + (fn [db [_ identity]] + (reaction (get-in @db [:contacts identity])))) + (register-sub :all-new-contacts (fn [db _] (contacts-by-current-chat remove db))) @@ -47,8 +83,8 @@ chat (reaction (get-in @db [:chats chat-id])) contacts (contacts-by-chat filter db chat-id)] (reaction - (when @chat - (if (:group-chat @chat) - ;; TODO return group chat icon - nil - (:photo-path (first @contacts)))))))) + (when @chat + (if (:group-chat @chat) + ;; TODO return group chat icon + nil + (:photo-path (first @contacts)))))))) diff --git a/src/status_im/contacts/views/contact.cljs b/src/status_im/contacts/views/contact.cljs index f253789990..b63ea9c31e 100644 --- a/src/status_im/contacts/views/contact.cljs +++ b/src/status_im/contacts/views/contact.cljs @@ -1,16 +1,35 @@ (ns status-im.contacts.views.contact (:require-macros [status-im.utils.views :refer [defview]]) - (:require [status-im.components.react :refer [view touchable-highlight]] + (:require [status-im.components.react :refer [view text icon touchable-highlight]] [re-frame.core :refer [dispatch subscribe]] + [status-im.contacts.styles :as st] [status-im.contacts.views.contact-inner :refer [contact-inner-view]])) +(defn letter-view [letter] + [view st/letter-container + (when letter + [text {:style st/letter-text} letter])]) + (defn on-press [chat whisper-identity] (if chat #(dispatch [:navigate-to :chat whisper-identity]) #(dispatch [:start-chat whisper-identity]))) -(defview contact-view [{:keys [whisper-identity] :as contact}] +(defview contact-with-letter-view [{:keys [whisper-identity letter] :as contact}] [chat [:get-chat whisper-identity]] [touchable-highlight {:onPress (on-press chat whisper-identity)} - [view {} [contact-inner-view contact]]]) + [view st/contact-container + [letter-view letter] + [contact-inner-view contact]]]) + +(defview contact-extended-view [{:keys [whisper-identity] :as contact} info more-click-handler] + [chat [:get-chat whisper-identity]] + [touchable-highlight + {:onPress (on-press chat whisper-identity)} + [view st/contact-container + [contact-inner-view contact info] + [touchable-highlight + {:on-press more-click-handler} + [view st/more-btn + [icon :more-vertical st/more-btn-icon]]]]]) diff --git a/src/status_im/contacts/views/contact_inner.cljs b/src/status_im/contacts/views/contact_inner.cljs index 05502d793b..4c27d5e1a1 100644 --- a/src/status_im/contacts/views/contact_inner.cljs +++ b/src/status_im/contacts/views/contact_inner.cljs @@ -1,31 +1,26 @@ (ns status-im.contacts.views.contact-inner (:require [clojure.string :as s] [status-im.components.react :refer [view image text]] - [status-im.resources :as res] + [status-im.components.chat-icon.screen :refer [contact-icon-contacts-tab]] [status-im.contacts.styles :as st] [status-im.i18n :refer [label]])) -(defn contact-photo [{:keys [photo-path]}] +(defn contact-photo [contact] [view st/contact-photo-container - [image {:source (if (s/blank? photo-path) - res/user-no-photo - {:uri photo-path}) - :style st/photo-image}]]) + [contact-icon-contacts-tab contact]]) -(defn contact-online [{:keys [online]}] - (when online - [view st/online-container - [view st/online-dot-left] - [view st/online-dot-right]])) - -(defn contact-inner-view [{:keys [name photo-path online]}] - [view st/contact-container - [view st/photo-container - [contact-photo {:photo-path photo-path}] - [contact-online {:online online}]] - [view st/name-container - [text {:style st/name-text} - (if (pos? (count name)) - name - ;; todo is this correct behaviour? - (label :t/no-name))]]]) +(defn contact-inner-view + ([contact] + (contact-inner-view contact nil)) + ([{:keys [name] :as contact} info] + [view st/contact-inner-container + [contact-photo contact] + [view st/info-container + [text {:style st/name-text} + (if (pos? (count (:name contact))) + name + ;; todo is this correct behaviour? + (label :t/no-name))] + (when info + [text {:style st/info-text} + info])]])) diff --git a/src/status_im/contacts/views/contact_list.cljs b/src/status_im/contacts/views/contact_list.cljs new file mode 100644 index 0000000000..9a346e3829 --- /dev/null +++ b/src/status_im/contacts/views/contact_list.cljs @@ -0,0 +1,46 @@ +(ns status-im.contacts.views.contact-list + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [status-im.components.react :refer [view text + image + touchable-highlight + list-view + list-item]] + [status-im.contacts.views.contact :refer [contact-with-letter-view]] + [status-im.components.toolbar :refer [toolbar]] + [status-im.components.drawer.view :refer [drawer-view open-drawer]] + [status-im.components.icons.ionicons :refer [icon]] + [status-im.components.styles :refer [color-blue + hamburger-icon + icon-search + create-icon + toolbar-background1]] + [status-im.contacts.styles :as st] + [status-im.utils.listview :as lw] + [status-im.i18n :refer [label]])) + +(defn render-row [row _ _] + (list-item [contact-with-letter-view row])) + +(defview contact-list-toolbar [] + [group [:get :contacts-group]] + [toolbar {:title (label (if (= group :dapps) + :t/contacs-group-dapps + :t/contacs-group-people)) + :background-color toolbar-background1 + :action {:image {:source {:uri :icon_search} + :style icon-search} + :handler (fn [])}}]) + +(defview contact-list [] + [contacts [:contacts-with-letters]] + [drawer-view + [view st/contacts-list-container + [contact-list-toolbar] + ;; todo what if there is no contacts, should we show some information + ;; about this? + (when contacts + [list-view {:dataSource (lw/to-datasource contacts) + :enableEmptySections true + :renderRow render-row + :style st/contacts-list}])]]) diff --git a/src/status_im/contacts/views/new_contact.cljs b/src/status_im/contacts/views/new_contact.cljs index 7e797f1f04..ee0ae60929 100644 --- a/src/status_im/contacts/views/new_contact.cljs +++ b/src/status_im/contacts/views/new_contact.cljs @@ -8,8 +8,8 @@ image linear-gradient touchable-highlight]] + [status-im.utils.identicon :refer [identicon]] [status-im.components.toolbar :refer [toolbar]] - [status-im.components.drawer.view :refer [drawer-view open-drawer]] [status-im.components.styles :refer [color-purple color-white icon-search @@ -59,20 +59,19 @@ (defview new-contact [] [{:keys [name whisper-identity phone-number] :as new-contact} [:get :new-contact]] - [drawer-view - [view st/contact-form-container - [toolbar {:background-color :white - :nav-action {:image {:source {:uri :icon_back} - :style icon-back} - :handler #(dispatch [:navigate-back])} - :custom-content toolbar-title - :action {:image {:source {:uri (if (s/valid? ::v/contact new-contact) - :icon_ok_blue - :icon_ok_disabled)} - :style icon-search} - :handler #(dispatch [:add-new-contact new-contact])}}] - [view st/form-container - [contact-name-input name] - [contact-whisper-id-input whisper-identity]] - [view st/address-explication-container - [text {:style st/address-explication} (label :t/address-explication)]]]]) + [view st/contact-form-container + [toolbar {:background-color :white + :nav-action {:image {:source {:uri :icon_back} + :style icon-back} + :handler #(dispatch [:navigate-back])} + :custom-content toolbar-title + :action {:image {:source {:uri (if (s/valid? ::v/contact new-contact) + :icon_ok_blue + :icon_ok_disabled)} + :style icon-search} + :handler #(dispatch [:add-new-contact (merge {:photo-path (identicon whisper-identity)} new-contact)])}}] + [view st/form-container + [contact-whisper-id-input whisper-identity] + [contact-name-input name]] + [view st/address-explication-container + [text {:style st/address-explication} (label :t/address-explication)]]]) diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 2b3ad6f88f..25772a58fa 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -44,7 +44,8 @@ :message-input-buttons-scale 1 :messages-offset 0 :commands-input-is-switching? false - :response-resize? false}}) + :response-resize? false + :tabs-bar-value (anim/create-value 0)}}) (def protocol-initialized-path [:protocol-initialized]) (defn chat-input-text-path [chat-id] diff --git a/src/status_im/discovery/styles.cljs b/src/status_im/discovery/styles.cljs index dd998b352d..540dc04a03 100644 --- a/src/status_im/discovery/styles.cljs +++ b/src/status_im/discovery/styles.cljs @@ -2,6 +2,7 @@ (:require [status-im.components.styles :refer [font title-font color-white + color-gray2 chat-background online-color selected-message-color @@ -49,7 +50,7 @@ :elevation 0}) (def discovery-subtitle - {:color "#8f838c93" + {:color color-gray2 :fontFamily "sans-serif-medium" :fontSize 14}) diff --git a/src/status_im/group_settings/styles/member.cljs b/src/status_im/group_settings/styles/member.cljs deleted file mode 100644 index e5ef3f9f58..0000000000 --- a/src/status_im/group_settings/styles/member.cljs +++ /dev/null @@ -1,78 +0,0 @@ -(ns status-im.group-settings.styles.member - (:require [status-im.components.styles :refer [font - title-font - text1-color - text2-color - color-white - online-color]])) - -(def contact-photo-container - {:borderRadius 50}) - -(def photo-image - {:borderRadius 50 - :width 40 - :height 40}) - -(def online-container - {:position :absolute - :top 24 - :left 24 - :width 20 - :height 20 - :borderRadius 50 - :backgroundColor online-color - :borderWidth 2 - :borderColor color-white}) - -(def online-dot - {:position :absolute - :top 6 - :width 4 - :height 4 - :borderRadius 50 - :backgroundColor color-white}) - -(def online-dot-left - (assoc online-dot :left 3)) - -(def online-dot-right - (assoc online-dot :left 9)) - -(def contact-container - {:flexDirection :row - :height 56}) - -(def photo-container - {:marginTop 8 - :marginLeft 16 - :width 44 - :height 44}) - -(def info-container - {:flex 1 - :flexDirection :column - :marginLeft 16 - :justifyContent :center}) - -(def name-text - {:marginTop -2 - :fontSize 16 - :fontFamily font - :color text1-color}) - -(def role-text - {:marginTop 1 - :fontSize 12 - :fontFamily font - :color text2-color}) - -(def more-btn - {:width 56 - :height 56 - :alignItems :center - :justifyContent :center }) - -(def more-btn-icon - {:width 4 - :height 16}) diff --git a/src/status_im/group_settings/views/member.cljs b/src/status_im/group_settings/views/member.cljs index c5292daa36..e73d46cc2a 100644 --- a/src/status_im/group_settings/views/member.cljs +++ b/src/status_im/group_settings/views/member.cljs @@ -1,44 +1,9 @@ (ns status-im.group-settings.views.member - (:require [clojure.string :as s] - [re-frame.core :refer [subscribe dispatch dispatch-sync]] - [status-im.components.react :refer [view - image - text - icon - touchable-highlight]] - [status-im.resources :as res] - [status-im.group-settings.styles.member :as st] + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [status-im.contacts.views.contact :refer [contact-extended-view]] [status-im.i18n :refer [label]])) -(defn contact-photo [{:keys [photo-path]}] - [view st/contact-photo-container - [image {:source (if (s/blank? photo-path) - res/user-no-photo - {:uri photo-path}) - :style st/photo-image}]]) - -(defn contact-online [{:keys [online]}] - (when online - [view st/online-container - [view st/online-dot-left] - [view st/online-dot-right]])) - -(defn member-view [{:keys [whisper-identity name photo-path online role]}] - [view st/contact-container - [view st/photo-container - [contact-photo {:photo-path photo-path}] - [contact-online {:online online}]] - [view st/info-container - [text {:style st/name-text} - (if (pos? (count name)) - name - ;; todo is this correct behaviour? - (label :t/no-name))] - ;; TODO implement :role property for group chat contact - (when role - [text {:style st/role-text} - role])] - [touchable-highlight - {:on-press #(dispatch [:set :selected-participants #{whisper-identity}])} - [view st/more-btn - [icon :more-vertical st/more-btn-icon]]]]) +(defn member-view [{:keys [whisper-identity role] :as contact}] + ;; TODO implement :role property for group chat contact + [contact-extended-view contact role + #(dispatch [:set :selected-participants #{whisper-identity}])]) diff --git a/src/status_im/models/contacts.cljs b/src/status_im/models/contacts.cljs index ec286b17a6..6c3ea62a7a 100644 --- a/src/status_im/models/contacts.cljs +++ b/src/status_im/models/contacts.cljs @@ -1,5 +1,6 @@ (ns status-im.models.contacts (:require [status-im.persistence.realm :as r] + [status-im.utils.identicon :refer [identicon]] [status-im.persistence.realm-queries :refer [include-query exclude-query]])) @@ -8,9 +9,9 @@ (r/sorted :name :asc) r/collection->map)) -(defn create-contact [{:keys [name photo-path] :as contact}] +(defn create-contact [{:keys [name photo-path whisper-identity] :as contact}] (->> {:name (or name "") - :photo-path (or photo-path "")} + :photo-path (or photo-path (identicon whisper-identity))} (merge contact) (r/create :contacts))) diff --git a/src/status_im/navigation/handlers.cljs b/src/status_im/navigation/handlers.cljs index aa9c1eee32..405d172f74 100644 --- a/src/status_im/navigation/handlers.cljs +++ b/src/status_im/navigation/handlers.cljs @@ -67,6 +67,12 @@ (fn [db _] (push-view db :contact-list))) +(register-handler :show-group-contacts + (fn [db [_ group]] + (-> db + (assoc :contacts-group group) + (push-view :group-contacts)))) + (defn show-profile [db [_ identity]] (-> db diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index 3206eeca42..c667275426 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -72,6 +72,10 @@ :contacts "Contacts" :no-name "Noname" :new-contact "New Contact" + :show-all "SHOW ALL" + :contacs-group-dapps "Dapps" + :contacs-group-people "People" + :no-contacts "No contacts yet" ;group-settings :remove "Remove" @@ -132,4 +136,4 @@ ;users :add-account "Add account" - }) \ No newline at end of file + }) diff --git a/src/status_im/utils/identicon.cljs b/src/status_im/utils/identicon.cljs new file mode 100644 index 0000000000..2d8dac64e1 --- /dev/null +++ b/src/status_im/utils/identicon.cljs @@ -0,0 +1,13 @@ +(ns status-im.utils.identicon + (:require [clojure.string :as s] + [status-im.utils.utils :as u])) + +(def default-size 40) + +(def identicon-js (u/require "identicon.js")) + +(defn identicon + ([hash] (identicon hash default-size)) + ([hash options] + (str "data:image/png;base64," (.toString (new identicon-js hash options))))) +