Andrey Shovkoplyas 2017-02-16 20:21:57 +03:00 committed by Roman Volosovskyi
parent 58f8eecffa
commit d17646a6e9
91 changed files with 1114 additions and 365 deletions

View File

@ -25,6 +25,7 @@
"identicon.js", "identicon.js",
"react-native-fs", "react-native-fs",
"react-native-dialogs", "react-native-dialogs",
"react-native-popup-menu",
"react-native-image-resizer", "react-native-image-resizer",
"react-native-image-crop-picker", "react-native-image-crop-picker",
"react-native-webview-bridge", "react-native-webview-bridge",

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_add_blue.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_back_dark.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_check_on.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_hamburger_dark.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_dots_horizontal_dark.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_dots_horizontal_gray.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_search_dark.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -65,6 +65,7 @@
"react-native-linear-gradient": "1.5.7", "react-native-linear-gradient": "1.5.7",
"react-native-network-info": "github:alwx/react-native-network-info", "react-native-network-info": "github:alwx/react-native-network-info",
"react-native-orientation": "github:youennPennarun/react-native-orientation", "react-native-orientation": "github:youennPennarun/react-native-orientation",
"react-native-popup-menu": "^0.7.1",
"react-native-qrcode": "^0.2.2", "react-native-qrcode": "^0.2.2",
"react-native-randombytes": "^2.1.0", "react-native-randombytes": "^2.1.0",
"react-native-share": "1.0.17", "react-native-share": "1.0.17",

View File

@ -12,6 +12,7 @@
splash-screen splash-screen
http-bridge]] http-bridge]]
[status-im.components.main-tabs :refer [main-tabs]] [status-im.components.main-tabs :refer [main-tabs]]
[status-im.components.context-menu :refer [menu-context]]
[status-im.contacts.search-results :refer [contacts-search-results]] [status-im.contacts.search-results :refer [contacts-search-results]]
[status-im.contacts.views.contact-list :refer [contact-list]] [status-im.contacts.views.contact-list :refer [contact-list]]
[status-im.contacts.views.new-contact :refer [new-contact]] [status-im.contacts.views.new-contact :refer [new-contact]]
@ -23,8 +24,9 @@
[status-im.accounts.screen :refer [accounts]] [status-im.accounts.screen :refer [accounts]]
[status-im.transactions.screen :refer [confirm]] [status-im.transactions.screen :refer [confirm]]
[status-im.chats-list.screen :refer [chats-list]] [status-im.chats-list.screen :refer [chats-list]]
[status-im.new-group.screen-private :refer [new-group]]
[status-im.new-group.screen-public :refer [new-public-group]] [status-im.new-group.screen-public :refer [new-public-group]]
[status-im.new-group.screen-private :refer [new-group contact-group]];; TODO: confusion with names
[status-im.new-group.views.contact-list :refer [contact-group-list]]
[status-im.participants.views.add :refer [new-participants]] [status-im.participants.views.add :refer [new-participants]]
[status-im.participants.views.remove :refer [remove-participants]] [status-im.participants.views.remove :refer [remove-participants]]
[status-im.group-settings.screen :refer [group-settings]] [status-im.group-settings.screen :refer [group-settings]]
@ -33,7 +35,8 @@
status-im.data-store.core status-im.data-store.core
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.components.status :as status] [status-im.components.status :as status]
[status-im.chat.styles.screen :as st] [status-im.components.styles :as st]
[status-im.chat.styles.screen :as chat-st]
[status-im.accounts.views.qr-code :refer [qr-code-view]])) [status-im.accounts.views.qr-code :refer [qr-code-view]]))
(defn init-back-button-handler! [] (defn init-back-button-handler! []
@ -105,6 +108,8 @@
:chat-list main-tabs :chat-list main-tabs
:new-group new-group :new-group new-group
:new-public-group new-public-group :new-public-group new-public-group
:contact-group contact-group
:contact-group-list contact-group-list
:group-settings group-settings :group-settings group-settings
:contact-list main-tabs :contact-list main-tabs
:contact-list-search-results contacts-search-results :contact-list-search-results contacts-search-results
@ -118,21 +123,20 @@
:login login :login login
:recover recover :recover recover
:my-profile my-profile)] :my-profile my-profile)]
[view [menu-context st/flex
{:flex 1} [view st/flex
[component] [component]
(when @modal-view (when @modal-view
[view [view chat-st/chat-modal
st/chat-modal [modal {:animation-type :slide
[modal {:animation-type :slide :transparent false
:transparent false :on-request-close #(dispatch [:navigate-back])}
:on-request-close #(dispatch [:navigate-back])} (let [component (case @modal-view
(let [component (case @modal-view :qr-scanner qr-scanner
:qr-scanner qr-scanner :qr-code-view qr-code-view
:qr-code-view qr-code-view :confirm confirm
:confirm confirm :contact-list-modal contact-list)]
:contact-list-modal contact-list)] [component])]])]]))))})))
[component])]])]))))})))
(defn init [] (defn init []
(status/call-module status/init-jail) (status/call-module status/init-jail)

View File

@ -1,6 +1,5 @@
(ns status-im.android.platform (ns status-im.android.platform
(:require [status-im.components.styles :as styles] (:require [status-im.components.styles :as styles]))
[status-im.components.toolbar.styles :refer [toolbar-background2]]))
(def component-styles (def component-styles
{:status-bar {:default {:height 0 {:status-bar {:default {:height 0
@ -8,7 +7,7 @@
:color styles/color-white} :color styles/color-white}
:main {:height 0 :main {:height 0
:bar-style "dark-content" :bar-style "dark-content"
:color toolbar-background2} :color styles/color-white}
:transparent {:height 20 :transparent {:height 20
:bar-style "light-content" :bar-style "light-content"
:translucent? true :translucent? true
@ -34,8 +33,17 @@
:item {:status-text {:color styles/color-black :item {:status-text {:color styles/color-black
:line-height 22 :line-height 22
:font-size 14}}} :font-size 14}}}
:contacts {:subtitle {:color styles/color-gray2 :contacts {:subtitle {:color styles/color-gray4
:font-size 14}} :font-size 14}
:separator {:height 0}
:icon-check {:border-radius 2
:width 17
:height 17}
:group-header {:flexDirection :row
:alignItems :center
:height 56
:padding-top 10
:backgroundColor styles/color-light-gray}}
:bottom-gradient {:height 3} :bottom-gradient {:height 3}
:input-label {:left 4} :input-label {:left 4}
:input-error-text {:margin-left 4} :input-error-text {:margin-left 4}
@ -47,7 +55,8 @@
:toolbar-last-activity {:color styles/text2-color :toolbar-last-activity {:color styles/text2-color
:background-color :transparent :background-color :transparent
:top 0 :top 0
:font-size 12}}) :font-size 12}
:toolbar-title-container {:padding-left 16}})
(def fonts (def fonts
{:light {:font-family "sans-serif-light"} {:light {:font-family "sans-serif-light"}
@ -56,7 +65,6 @@
:toolbar-title {:font-family "sans-serif"}}) :toolbar-title {:font-family "sans-serif"}})
;; Dialogs ;; Dialogs
(def react-native-dialogs (js/require "react-native-dialogs")) (def react-native-dialogs (js/require "react-native-dialogs"))

View File

@ -108,8 +108,7 @@
[db [_ _ {:keys [handler action params]}]] [db [_ _ {:keys [handler action params]}]]
(assoc db :contacts-click-handler handler (assoc db :contacts-click-handler handler
:contacts-click-action action :contacts-click-action action
:contacts-click-params params :contacts-click-params params))
:contacts-filter #(not (nil? (:address %)))))
(def qr-context {:toolbar-title (label :t/address)}) (def qr-context {:toolbar-title (label :t/address)})

View File

@ -9,8 +9,10 @@
[status-im.commands.utils :refer [reg-handler]] [status-im.commands.utils :refer [reg-handler]]
[status-im.constants :refer [console-chat-id wallet-chat-id]] [status-im.constants :refer [console-chat-id wallet-chat-id]]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.i18n :refer [label]]
[status-im.utils.homoglyph :as h] [status-im.utils.homoglyph :as h]
[status-im.utils.js-resources :as js-res])) [status-im.utils.js-resources :as js-res]
[status-im.utils.random :as random]))
(def commands-js "commands.js") (def commands-js "commands.js")
@ -164,3 +166,28 @@
(reg-handler ::clear-commands-callbacks (reg-handler ::clear-commands-callbacks
(fn [db [chat-id]] (fn [db [chat-id]]
(assoc-in db [::commands-callbacks chat-id] nil))) (assoc-in db [::commands-callbacks chat-id] nil)))
(reg-handler :load-default-contacts!
(u/side-effect!
(fn [{:keys [chats groups]}]
(let [default-contacts js-res/default-contacts
default-dapps-group-contacts (mapv #(hash-map :identity (clojure.core/name (first %)))
(filter #(true? (:dapp? (second %))) default-contacts))]
(doseq [[id {:keys [name photo-path public-key add-chat?
dapp? dapp-url dapp-hash]}] default-contacts]
(let [id' (clojure.core/name id)]
(when-not (chats id')
(when add-chat?
(dispatch [:add-chat id' {:name (:en name)}]))
(dispatch [:add-contacts [{:whisper-identity id'
:name (:en name)
:photo-path photo-path
:public-key public-key
:dapp? dapp?
:dapp-url (:en dapp-url)
:dapp-hash dapp-hash}]]))))
(dispatch [:add-groups [{:group-id "dapps"
:name (label :t/contacts-group-dapps)
:order 0
:timestamp (random/timestamp)
:contacts default-dapps-group-contacts}]])))))

View File

@ -0,0 +1,10 @@
(ns status-im.components.confirm-button
(:require [status-im.components.styles :as st]
[status-im.components.react :refer [view
text
touchable-highlight]]))
(defn confirm-button [label on-press]
[touchable-highlight {:on-press on-press}
[view st/confirm-button
[text {:style st/confirm-button-label} label]]])

View File

@ -0,0 +1,49 @@
(ns status-im.components.context-menu
(:require [reagent.core :as r]
[status-im.components.styles :as st]
[status-im.i18n :refer [label]]
[status-im.utils.platform :refer [platform-specific ios?]]
[re-frame.core :refer [dispatch]]
[status-im.components.react :refer [view touchable-highlight]]))
(def react-native-popup-menu (js/require "react-native-popup-menu"))
(defn get-property [name]
(aget react-native-popup-menu name))
(defn adapt-class [class]
(when class
(r/adapt-react-class class)))
(defn get-class [name]
(adapt-class (get-property name)))
(def menu (get-class "Menu"))
(def menu-context (get-class "MenuContext"))
(def menu-trigger (get-class "MenuTrigger"))
(def menu-options (get-class "MenuOptions"))
(def menu-option (get-class "MenuOption"))
(def list-selection-fn (:list-selection-fn platform-specific))
(defn open-ios-menu [options]
(list-selection-fn {:options (mapv :text options)
:callback (fn [index]
(when (< index (count options))
(when-let [handler (:value (nth options index))]
(handler))))
:cancel-text (label :t/cancel)})
nil)
(defn context-menu [trigger options]
(if ios?
[touchable-highlight
{:on-press #(open-ios-menu options)}
[view
trigger]]
[menu {:onSelect #(when % (do (%) nil))}
[menu-trigger trigger]
[menu-options st/context-menu
(for [option options]
^{:key option}
[menu-option option])]]))

View File

@ -85,7 +85,7 @@
(assoc :style (merge style font))))])) (assoc :style (merge style font))))]))
(defn icon (defn icon
([n] (icon n {})) ([n] (icon n st/icon-default))
([n style] ([n style]
[image {:source {:uri (keyword (str "icon_" (name n)))} [image {:source {:uri (keyword (str "icon_" (name n)))}
:style style}])) :style style}]))

View File

@ -8,11 +8,16 @@
(def color-gray "#838c93de") (def color-gray "#838c93de")
(def color-gray2 "#8f838c93") (def color-gray2 "#8f838c93")
(def color-gray3 "#00000040") (def color-gray3 "#00000040")
(def color-gray4 "#939ba1")
(def color-gray5 "#d9dae1")
(def color-steel "#838b91") (def color-steel "#838b91")
(def color-white "white") (def color-white "white")
(def color-light-blue-transparent "#bbc4cb32") (def color-light-blue "#628fe3")
(def color-light-blue2 "#eff3fc")
(def color-light-gray "#EEF2F5") (def color-light-gray "#EEF2F5")
(def color-red "red") (def color-red "red")
(def color-light-red "#e86363")
(def color-separator "#D6D6D6") (def color-separator "#D6D6D6")
(def text1-color color-black) (def text1-color color-black)
@ -23,15 +28,16 @@
(def new-messages-count-color color-blue-transparent) (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 selected-contact-color color-light-blue2)
(def separator-color color-gray4)
(def default-chat-color color-purple) (def default-chat-color color-purple)
(def flex (def flex
{:flex 1}) {:flex 1})
(def icon-search (def icon-search
{:width 17 {:width 24
:height 17}) :height 24})
(def create-icon (def create-icon
{:fontSize 20 {:fontSize 20
@ -39,8 +45,12 @@
:color :white}) :color :white})
(def icon-back (def icon-back
{:width 8 {:width 24
:height 14}) :height 24})
(def icon-default
{:width 24
:height 24})
(def icon-add (def icon-add
{:width 14 {:width 14
@ -78,3 +88,25 @@
(def button-input (def button-input
{:flex 1 {:flex 1
:flexDirection :column}) :flexDirection :column})
(def confirm-button
{:flex-direction :row
:height 52
:justify-content :center
:align-items :center
:background-color color-light-blue})
(def confirm-button-label
{:color color-white
:font-size 17
:line-height 20
:letter-spacing -0.2})
(def context-menu
{:customStyles {:optionsContainer {:padding-top 8
:padding-bottom 8}
:optionWrapper {:padding-left 16
:justify-content :center
:height 48}
:text {:font-size 15
:line-height 20}}})

View File

@ -3,29 +3,34 @@
(def nothing (def nothing
{:image {:source nil {:image {:source nil
:style st/action-search}}) :style st/action-default}})
(defn hamburger [handler] (defn hamburger [handler]
{:image {:source {:uri :icon_hamburger} {:image {:source {:uri :icon_hamburger_dark}
:style st/action-hamburger} :style st/action-default}
:handler handler}) :handler handler})
(defn add [handler] (defn add [handler]
{:image {:source {:uri :icon_add} {:image {:source {:uri :icon_add}
:style st/action-add} :style st/action-default}
:handler handler})
(defn opts [handler]
{:image {:source {:uri :icon_options_dark}
:style st/action-default}
:handler handler}) :handler handler})
(defn search [handler] (defn search [handler]
{:image {:source {:uri :icon_search} {:image {:source {:uri :icon_search_dark}
:style st/action-search} :style st/action-default}
:handler handler}) :handler handler})
(defn back [handler] (defn back [handler]
{:image {:source {:uri :icon_back} {:image {:source {:uri :icon_back_dark}
:style st/action-back} :style st/action-default}
:handler handler}) :handler handler})
(defn back-white [handler] (defn back-white [handler]
{:image {:source {:uri :icon_back_white} {:image {:source {:uri :icon_back_white}
:style st/action-back} :style st/action-default}
:handler handler}) :handler handler})

View File

@ -2,15 +2,17 @@
(:require [status-im.components.styles :refer [text1-color (:require [status-im.components.styles :refer [text1-color
color-white color-white
color-light-gray color-light-gray
color-gray5
color-blue color-blue
color-black]])) color-black]]
[status-im.utils.platform :as p]))
(def toolbar-background1 color-white) (def toolbar-background1 color-white)
(def toolbar-background2 color-light-gray) (def toolbar-background2 color-light-gray)
(def toolbar-height 56) (def toolbar-height 56)
(def toolbar-icon-width 32) (def toolbar-icon-width 24)
(def toolbar-icon-spacing 8) (def toolbar-icon-spacing 24)
(def toolbar-gradient (def toolbar-gradient
{:height 4}) {:height 4})
@ -23,6 +25,11 @@
{:flex-direction :row {:flex-direction :row
:height toolbar-height}) :height toolbar-height})
(def toolbar-line
{:height 1
:background-color color-gray5
:opacity 0.4})
(defn toolbar-nav-actions-container [actions] (defn toolbar-nav-actions-container [actions]
{:width (when (and actions (> (count actions) 0)) {:width (when (and actions (> (count actions) 0))
(-> (+ toolbar-icon-width toolbar-icon-spacing) (-> (+ toolbar-icon-width toolbar-icon-spacing)
@ -38,9 +45,9 @@
:padding-right 12}) :padding-right 12})
(def toolbar-title-container (def toolbar-title-container
{:flex 1 (merge (get-in p/platform-specific [:component-styles :toolbar-title-container])
:alignItems :center {:flex 1
:justifyContent :center}) :justifyContent :center}))
(def toolbar-title-text (def toolbar-title-text
{:margin-top 0 {:margin-top 0
@ -61,12 +68,11 @@
:justify-content :center}) :justify-content :center})
(def toolbar-with-search (def toolbar-with-search
{:background-color toolbar-background2 {:background-color toolbar-background1
:elevation 0}) :elevation 0})
(def toolbar-with-search-content (def toolbar-with-search-content
{:flex 1 {:flex 1
:align-items :center
:justify-content :center}) :justify-content :center})
(def toolbar-search-input (def toolbar-search-input
@ -79,25 +85,8 @@
(def toolbar-with-search-title (def toolbar-with-search-title
{:color color-black {:color color-black
:align-self :center
:text-align :center
:font-size 16}) :font-size 16})
(def action-default
;; Specific actions {:width 24
:height 24})
(def action-hamburger
{:width 16
:height 12})
(def action-add
{:width 17
:height 17})
(def action-search
{:width 17
:height 17})
(def action-back
{:width 8
:height 14})

View File

@ -7,7 +7,7 @@
image image
touchable-highlight]] touchable-highlight]]
[status-im.components.sync-state.gradient :refer [sync-state-gradient-view]] [status-im.components.sync-state.gradient :refer [sync-state-gradient-view]]
[status-im.components.styles :refer [icon-back [status-im.components.styles :refer [icon-default
icon-search]] icon-search]]
[status-im.components.toolbar.actions :as act] [status-im.components.toolbar.actions :as act]
[status-im.components.toolbar.styles :as st] [status-im.components.toolbar.styles :as st]
@ -34,8 +34,8 @@
[touchable-highlight {:on-press #(dispatch [:navigate-back]) [touchable-highlight {:on-press #(dispatch [:navigate-back])
:accessibility-label id/toolbar-back-button} :accessibility-label id/toolbar-back-button}
[view (get-in platform-specific [:component-styles :toolbar-nav-action]) [view (get-in platform-specific [:component-styles :toolbar-nav-action])
[image {:source {:uri :icon_back} [image {:source {:uri :icon_back_dark}
:style icon-back}]]]))] :style icon-default}]]]))]
(or custom-content (or custom-content
[view {:style st/toolbar-title-container} [view {:style st/toolbar-title-container}
[text {:style st/toolbar-title-text [text {:style st/toolbar-title-text
@ -50,7 +50,8 @@
[view st/toolbar-action [view st/toolbar-action
[image action-image]]]) [image action-image]]])
custom-action)]] custom-action)]]
[sync-state-gradient-view]])) [sync-state-gradient-view]
[view st/toolbar-line]]))
(defn- toolbar-search-submit [on-search-submit] (defn- toolbar-search-submit [on-search-submit]
(let [text @(subscribe [:get-in [:toolbar-search :text]])] (let [text @(subscribe [:get-in [:toolbar-search :text]])]
@ -67,10 +68,7 @@
{:style st/toolbar-search-input {:style st/toolbar-search-input
:auto-focus true :auto-focus true
:placeholder search-placeholder :placeholder search-placeholder
:return-key-type "search" :on-change-text #(dispatch [:set-in [:toolbar-search :text] %])}]
:on-blur #(dispatch [:set-in [:toolbar-search :show] nil])
:on-change-text #(dispatch [:set-in [:toolbar-search :text] %])
:on-submit-editing #(toolbar-search-submit on-search-submit)}]
[view [view
[text {:style st/toolbar-with-search-title [text {:style st/toolbar-with-search-title
:font :toolbar-title} :font :toolbar-title}
@ -83,9 +81,11 @@
style style
on-search-submit] on-search-submit]
:as opts}] :as opts}]
(let [toggle-search-fn #(dispatch [:set-in [:toolbar-search :show] %]) (let [toggle-search-fn #(do
(dispatch [:set-in [:toolbar-search :show] %])
(dispatch [:set-in [:toolbar-search :text] ""]))
actions (if-not show-search? actions (if-not show-search?
(into actions [(act/search #(toggle-search-fn search-key))]))] (into [(act/search #(toggle-search-fn search-key))] actions))]
[toolbar {:style (merge st/toolbar-with-search style) [toolbar {:style (merge st/toolbar-with-search style)
:nav-action (if show-search? :nav-action (if show-search?
(act/back #(toggle-search-fn nil)) (act/back #(toggle-search-fn nil))

View File

@ -17,12 +17,24 @@
[status-im.utils.js-resources :as js-res])) [status-im.utils.js-resources :as js-res]))
(defmethod nav/preload-data! :group-contacts (defmethod nav/preload-data! :group-contacts
[db [_ _ group show-search?]]
(-> db
(assoc :contacts-group group)
(update :toolbar-search assoc
:show (when show-search? :contact-list)
:text "")))
(defmethod nav/preload-data! :contact-group
[db [_ _ group]] [db [_ _ group]]
(dissoc (if group
(if group (-> db
(assoc db :contacts-group group) (assoc :contact-group group
db) :selected-contacts (into #{} (map :identity (:contacts group)))
:contacts-filter)) :new-chat-name (:name group))
(update :toolbar-search assoc
:show :contact-list
:text ""))
db))
(defmethod nav/preload-data! :new-group (defmethod nav/preload-data! :new-group
[db _] [db _]
@ -34,8 +46,7 @@
[db [_ _ click-handler]] [db [_ _ click-handler]]
(-> db (-> db
(assoc-in [:toolbar-search :show] nil) (assoc-in [:toolbar-search :show] nil)
(assoc :contacts-click-handler click-handler (assoc :contacts-click-handler click-handler)))
:contacts-filter nil)))
(register-handler :remove-contacts-click-handler (register-handler :remove-contacts-click-handler
(fn [db] (fn [db]
@ -167,16 +178,15 @@
(defn save-contacts! [{:keys [new-contacts]} _] (defn save-contacts! [{:keys [new-contacts]} _]
(contacts/save-all new-contacts)) (contacts/save-all new-contacts))
(defn update-pending-status [old-contacts {:keys [whisper-identity pending] :as contact}] (defn update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
(let [{old-pending :pending (let [{old-pending :pending?
:as old-contact} (get old-contacts whisper-identity)] :as old-contact} (get old-contacts whisper-identity)
(if old-contact pending?' (if old-contact (and old-pending pending?) pending?)]
(assoc contact :pending (and old-pending pending)) (assoc contact :pending? (boolean pending?'))))
(assoc contact :pending pending))))
(defn add-new-contacts (defn add-new-contacts
[{:keys [contacts] :as db} [_ new-contacts]] [{:keys [contacts] :as db} [_ new-contacts]]
(let [identities (set (map :whisper-identity contacts)) (let [identities (set (keys contacts))
new-contacts' (->> new-contacts new-contacts' (->> new-contacts
(map #(update-pending-status contacts %)) (map #(update-pending-status contacts %))
(remove #(identities (:whisper-identity %))) (remove #(identities (:whisper-identity %)))
@ -243,9 +253,9 @@
(register-handler :add-pending-contact (register-handler :add-pending-contact
(u/side-effect! (u/side-effect!
(fn [{:keys [chats contacts]} [_ chat-id]] (fn [{:keys [chats contacts]} [_ chat-id]]
(let [contact (if-let [contact-info (get-in chats [chat-id :contact-info])] (let [contact (if-let [contact-info (get-in chats [chat-id :contact-info])]
(read-string contact-info) (read-string contact-info)
(assoc (get contacts chat-id) :pending false)) (assoc (get contacts chat-id) :pending? false))
contact' (assoc contact :address (public-key->address chat-id))] contact' (assoc contact :address (public-key->address chat-id))]
(dispatch [::prepare-contact contact']) (dispatch [::prepare-contact contact'])
(dispatch [:watch-contact contact']) (dispatch [:watch-contact contact'])
@ -283,6 +293,8 @@
(let [{{:keys [public private]} :keypair (let [{{:keys [public private]} :keypair
timestamp :timestamp} payload timestamp :timestamp} payload
prev-last-updated (get-in db [:contacts from :keys-last-updated])] prev-last-updated (get-in db [:contacts from :keys-last-updated])]
(when (<= prev-last-updated timestamp) (when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from (let [contact {:whisper-identity from
:public-key public :public-key public
@ -305,9 +317,19 @@
(after stop-watching-contact) (after stop-watching-contact)
(u/side-effect! (u/side-effect!
(fn [_ [_ {:keys [whisper-identity] :as contact}]] (fn [_ [_ {:keys [whisper-identity] :as contact}]]
(dispatch [:update-contact! (assoc contact :pending true)]) (dispatch [:update-contact! (assoc contact :pending? true)])
(dispatch [:account-update-keys])))) (dispatch [:account-update-keys]))))
(defn remove-contact-from-group [whisper-identity]
(fn [contacts]
(remove #(= whisper-identity (:identity %)) contacts)))
(register-handler :remove-contact-from-group
(u/side-effect!
(fn [_ [_ {:keys [whisper-identity]} group]]
(let [group' (update group :contacts (remove-contact-from-group whisper-identity))]
(dispatch [:update-group group'])))))
(register-handler :remove-contact (register-handler :remove-contact
(fn [db [_ whisper-identity pred]] (fn [db [_ whisper-identity pred]]
(if-let [contact (contacts/get-by-id whisper-identity)] (if-let [contact (contacts/get-by-id whisper-identity)]
@ -329,3 +351,14 @@
0 (dispatch [:hide-contact contact]) 0 (dispatch [:hide-contact contact])
:default)) :default))
:cancel-text (label :t/cancel)})))) :cancel-text (label :t/cancel)}))))
(register-handler
:open-contact-group-list
(after #(dispatch [:navigate-to :contact-group-list]))
(fn [db _]
(->
(assoc db :contact-group nil
:selected-contacts #{}
:new-chat-name "")
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:toolbar-search :text] ""))))

View File

@ -6,6 +6,7 @@
[status-im.components.react :refer [view [status-im.components.react :refer [view
text text
image image
icon
touchable-highlight touchable-highlight
linear-gradient linear-gradient
scroll-view scroll-view
@ -14,10 +15,12 @@
[status-im.components.action-button :refer [action-button [status-im.components.action-button :refer [action-button
action-button-item]] action-button-item]]
[status-im.components.status-bar :refer [status-bar]] [status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar-with-search]] [status-im.components.toolbar.view :refer [toolbar-with-search toolbar]]
[status-im.components.toolbar.actions :as act] [status-im.components.toolbar.actions :as act]
[status-im.components.toolbar.styles :as tst]
[status-im.components.drawer.view :refer [open-drawer]] [status-im.components.drawer.view :refer [open-drawer]]
[status-im.components.icons.custom-icons :refer [ion-icon]] [status-im.components.icons.custom-icons :refer [ion-icon]]
[status-im.components.context-menu :refer [context-menu]]
[status-im.contacts.views.contact :refer [contact-view]] [status-im.contacts.views.contact :refer [contact-view]]
[status-im.utils.platform :refer [platform-specific]] [status-im.utils.platform :refer [platform-specific]]
[status-im.i18n :refer [label]] [status-im.i18n :refer [label]]
@ -25,30 +28,45 @@
[status-im.components.styles :refer [color-blue [status-im.components.styles :refer [color-blue
create-icon]])) create-icon]]))
(def contacts-limit 50) (def contacts-limit 5)
(defn toolbar-view [show-search?] (def toolbar-options
(let [new-contact? (get-in platform-specific [:contacts :new-contact-in-toolbar?]) [{:text (label :t/new-contact) :value #(dispatch [:navigate-to :new-contact])}
actions (if new-contact? {:text (label :t/edit) :value #(dispatch [:set-in [:contacts-ui-props :edit?] true])}
[(act/add #(dispatch [:navigate-to :new-contact]))])] {:text (label :t/new-group) :value #(dispatch [:open-contact-group-list])}])
(toolbar-with-search
{:show-search? show-search?
:search-key :contact-list
:title (label :t/contacts)
:search-placeholder (label :t/search-for)
:nav-action (act/hamburger open-drawer)
:actions actions
:on-search-submit (fn [text]
(when-not (str/blank? text)
(dispatch [:set :contacts-filter #(let [name (-> (or (:name %) "")
(str/lower-case))
text (str/lower-case text)]
(not= (.indexOf name text) -1))])
(dispatch [:set :contact-list-search-text text])
(dispatch [:navigate-to :contact-list-search-results])))})))
(defn subtitle-view [subtitle contacts-count] (defn toolbar-actions []
[view st/contact-group-header-inner (let [new-contact? (get-in platform-specific [:contacts :new-contact-in-toolbar?])]
[view st/toolbar-actions
[touchable-highlight
{:on-press #(dispatch [:navigate-to :group-contacts nil :show-search])}
[view st/search-btn
[icon :search_dark]]]
[view st/more-btn
[context-menu
[icon :options_dark]
(if new-contact? toolbar-options (rest toolbar-options))]]]))
(defn toolbar-view []
[toolbar {:style tst/toolbar-with-search
:title (label :t/contacts)
:nav-action (act/hamburger open-drawer)
:custom-action (toolbar-actions)}])
(defn toolbar-edit []
[toolbar {:style tst/toolbar-with-search
:nav-action (act/back #(dispatch [:set-in [:contacts-ui-props :edit?] false]))
:title (label :t/edit-contacts)}])
(defn options-btn [group]
(let [options [{:value #(dispatch [:navigate-to :contact-group group]) :text (label :t/edit-group)}]]
[view st/more-btn
[context-menu
[icon :options_gray]
options]]))
(defn subtitle-view [subtitle contacts-count group extended?]
[view (get-in platform-specific [:component-styles :contacts :group-header])
[text {:style (merge st/contact-group-subtitle [text {:style (merge st/contact-group-subtitle
(get-in platform-specific [:component-styles :contacts :subtitle])) (get-in platform-specific [:component-styles :contacts :subtitle]))
:uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?]) :uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?])
@ -58,7 +76,9 @@
(get-in platform-specific [:component-styles :contacts :subtitle])) (get-in platform-specific [:component-styles :contacts :subtitle]))
:uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?]) :uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?])
:font :medium} :font :medium}
(str contacts-count)]]) (str contacts-count)]
(when extended?
[options-btn group])])
(defn group-top-view [] (defn group-top-view []
[linear-gradient {:style st/contact-group-header-gradient-bottom [linear-gradient {:style st/contact-group-header-gradient-bottom
@ -68,37 +88,47 @@
[linear-gradient {:style st/contact-group-header-gradient-top [linear-gradient {:style st/contact-group-header-gradient-top
:colors st/contact-group-header-gradient-top-colors}]) :colors st/contact-group-header-gradient-top-colors}])
(defn line-view [] (defn on-scroll-animation [e show-toolbar-shadow?]
[view {:style {:background-color "#D7D7D7" (let [offset (.. e -nativeEvent -contentOffset -y)]
:height 1}}]) (reset! show-toolbar-shadow? (pos? offset))))
(defn contact-group-view [contacts contacts-count subtitle group click-handler] (defn contact-group-form [{:keys [contacts contacts-count group edit? click-handler]}]
(let [shadows? (get-in platform-specific [:contacts :group-block-shadows?])] (let [shadows? (get-in platform-specific [:contacts :group-block-shadows?])
subtitle (:name group)]
[view st/contact-group [view st/contact-group
[view st/contact-group-header (when subtitle
[subtitle-view subtitle contacts-count]] [subtitle-view subtitle contacts-count group edit?])
(if shadows? (when (and subtitle shadows?)
[group-top-view] [group-top-view])
[line-view])
[view [view
(doall (doall
(map (fn [contact] (map (fn [contact]
^{:key contact} ^{:key contact}
[contact-view {:contact contact [contact-view
:extended? true {:contact contact
:on-click click-handler :extended? edit?
:more-on-click nil}]) :on-click (when-not edit? click-handler)
:extend-options (when group
[{:value #(dispatch [:hide-contact contact])
:text (label :t/delete-contact)}
{:value #(dispatch [:remove-contact-from-group contact group])
:text (label :t/remove-from-group)}])}])
contacts))] contacts))]
(when (<= contacts-limit (count contacts)) (when (< contacts-limit contacts-count)
[view st/show-all [view st/show-all
[touchable-highlight {:on-press #(dispatch [:navigate-to :group-contacts group])} [touchable-highlight (when-not edit? {:on-press #(dispatch [:navigate-to :group-contacts group])})
[view [view
[text {:style st/show-all-text [text {:style st/show-all-text
:font :medium} :font :medium}
(label :t/show-all)]]]]) (str (- contacts-count contacts-limit) " " (label :t/more))]]]])
(if shadows? (when shadows?
[group-bottom-view] [group-bottom-view])]))
[line-view])]))
(defview contact-group-view [{:keys [group] :as params}]
[contacts [:all-added-group-contacts-with-limit (:group-id group) contacts-limit]
contacts-count [:all-added-group-contacts-count (:group-id group)]]
[contact-group-form (merge params {:contacts contacts
:contacts-count contacts-count})])
(defn contacts-action-button [] (defn contacts-action-button []
[action-button {:button-color color-blue [action-button {:button-color color-blue
@ -113,44 +143,35 @@
[ion-icon {:name :md-create [ion-icon {:name :md-create
:style create-icon}]]]) :style create-icon}]]])
(defn contact-list [_] (defview contact-list [current-view?]
(let [peoples (subscribe [:get-added-people-with-limit contacts-limit]) [contacts [:get-added-contacts-with-limit contacts-limit]
dapps (subscribe [:get-added-dapps-with-limit contacts-limit]) contacts-count [:added-contacts-count]
people-count (subscribe [:added-people-count]) click-handler [:get :contacts-click-handler]
dapps-count (subscribe [:added-dapps-count]) edit? [:get-in [:contacts-ui-props :edit?]]
click-handler (subscribe [:get :contacts-click-handler]) groups [:all-added-groups]
show-search (subscribe [:get-in [:toolbar-search :show]]) show-toolbar-shadow? (r/atom false)]
show-toolbar-shadow? (r/atom false)] [view st/contacts-list-container
(fn [current-view?] (if edit?
[view st/contacts-list-container [toolbar-edit]
[toolbar-view (and current-view? [toolbar-view])
(= @show-search :contact-list))] (when @show-toolbar-shadow?
[view {:style st/toolbar-shadow} [linear-gradient {:style st/contact-group-header-gradient-bottom
(when @show-toolbar-shadow? :colors st/contact-group-header-gradient-bottom-colors}])
[linear-gradient {:style st/contact-group-header-gradient-bottom (if (pos? (+ (count groups) contacts-count))
:colors st/contact-group-header-gradient-bottom-colors}])] [scroll-view {:style st/contact-groups
(if (pos? (+ @dapps-count @people-count)) :onScroll #(on-scroll-animation % show-toolbar-shadow?)}
[scroll-view {:style st/contact-groups (when (pos? contacts-count)
:onScroll (fn [e] [contact-group-form {:contacts contacts
(let [offset (.. e -nativeEvent -contentOffset -y)] :contacts-count contacts-count
(reset! show-toolbar-shadow? :edit? edit?
(<= st/contact-group-header-height offset))))} :click-handler click-handler}])
(when (pos? @dapps-count) (for [group groups]
[contact-group-view ^{:key group}
@dapps [contact-group-view {:group group
@dapps-count :edit? edit?
(label :t/contacts-group-dapps) :click-handler click-handler}])]
:dapps [view st/empty-contact-groups
@click-handler]) [react/icon :group_big st/empty-contacts-icon]
(when (pos? @people-count) [text {:style st/empty-contacts-text} (label :t/no-contacts)]])
[contact-group-view (when (and (not edit?) (get-in platform-specific [:contacts :action-button?]))
@peoples [contacts-action-button])])
@people-count
(label :t/contacts-group-people)
:people
@click-handler])]
[view st/empty-contact-groups
[react/icon :group_big st/empty-contacts-icon]
[text {:style st/empty-contacts-text} (label :t/no-contacts)]])
(when (get-in platform-specific [:contacts :action-button?])
[contacts-action-button])])))

View File

@ -6,7 +6,7 @@
color-separator color-separator
color-gray2 color-gray2
color-gray]] color-gray]]
[status-im.components.toolbar.styles :refer [toolbar-background2]] [status-im.components.toolbar.styles :refer [toolbar-background1 toolbar-background2]]
[status-im.utils.platform :as p])) [status-im.utils.platform :as p]))
;; Contacts list ;; Contacts list
@ -15,6 +15,9 @@
{:height 2 {:height 2
:background-color toolbar-background2}) :background-color toolbar-background2})
(def toolbar-actions
{:flex-direction :row})
(def contact-groups (def contact-groups
{:flex 1 {:flex 1
:background-color toolbar-background2}) :background-color toolbar-background2})
@ -43,22 +46,13 @@
(def contact-group (def contact-group
{:flex-direction :column}) {:flex-direction :column})
(def contact-group-header
{:flex-direction :column
:background-color toolbar-background2})
(def contact-group-header-inner
{:flexDirection :row
:alignItems :center
:height 48
:backgroundColor toolbar-background2})
(def contact-group-subtitle (def contact-group-subtitle
{:flex 1 {:margin-left 16})
:margin-left 16})
(def contact-group-count (def contact-group-count
{:margin-right 14}) {:flex 1
:margin-left 8
:opacity 0.6})
(def contact-group-header-gradient-top (def contact-group-header-gradient-top
{:flexDirection :row {:flexDirection :row
@ -79,9 +73,6 @@
["rgba(24, 52, 76, 0.01)" ["rgba(24, 52, 76, 0.01)"
"rgba(24, 52, 76, 0.05)"]) "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 (def show-all
{:flexDirection :row {:flexDirection :row
:alignItems :center :alignItems :center
@ -95,6 +86,9 @@
;; ios only: ;; ios only:
:letterSpacing 0.5}) :letterSpacing 0.5})
(def contact-separator-container
{:background-color color-white})
(def contact-container (def contact-container
{:flex-direction :row {:flex-direction :row
:background-color color-white}) :background-color color-white})
@ -129,8 +123,8 @@
(def option-inner-image (def option-inner-image
{:width 24 {:width 24
:height 18 :height 18
:top 16 :top 16
:left 13}) :left 13})
(def group-icon (def group-icon
(assoc option-inner-image (assoc option-inner-image
@ -167,14 +161,18 @@
:color text2-color}) :color text2-color})
(def more-btn (def more-btn
{:width 56 {:width 24
:height 56 :height 56
:margin-right 14
:alignItems :center :alignItems :center
:justifyContent :center}) :justifyContent :center})
(def more-btn-icon (def search-btn
{:width 4 {:width 24
:height 16}) :height 56
:margin-right 24
:alignItems :center
:justifyContent :center})
; New contact ; New contact

View File

@ -1,7 +1,8 @@
(ns status-im.contacts.subs (ns status-im.contacts.subs
(:require-macros [reagent.ratom :refer [reaction]]) (:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub subscribe]] (:require [re-frame.core :refer [register-sub subscribe]]
[status-im.utils.identicon :refer [identicon]])) [status-im.utils.identicon :refer [identicon]]
[clojure.string :as str]))
(register-sub :current-contact (register-sub :current-contact
(fn [db [_ k]] (fn [db [_ k]]
@ -29,47 +30,66 @@
(sort-contacts) (sort-contacts)
(reaction))))) (reaction)))))
(register-sub :all-added-people (register-sub :all-added-group-contacts
(fn [] (fn [db [_ group-id]]
(let [contacts (subscribe [:all-added-contacts])] (let [contacts (subscribe [:all-added-contacts])
(reaction (remove :dapp? @contacts))))) group-contacts (reaction (into #{} (map #(:identity %)
(get-in @db [:contact-groups group-id :contacts]))))]
(reaction (filter #(@group-contacts (:whisper-identity %)) @contacts)))))
(register-sub :all-added-dapps (register-sub :all-added-group-contacts-with-limit
(fn [] (fn [db [_ group-id limit]]
(let [contacts (subscribe [:all-added-contacts])] (let [contacts (subscribe [:all-added-group-contacts group-id])]
(reaction (filter :dapp? @contacts)))))
(register-sub :get-added-people-with-limit
(fn [_ [_ limit]]
(let [contacts (subscribe [:all-added-people])]
(reaction (take limit @contacts))))) (reaction (take limit @contacts)))))
(register-sub :get-added-dapps-with-limit (register-sub :all-added-group-contacts-count
(fn [_ [_ group-id]]
(let [contacts (subscribe [:all-added-group-contacts group-id])]
(reaction (count @contacts)))))
(register-sub :get-added-contacts-with-limit
(fn [_ [_ limit]] (fn [_ [_ limit]]
(let [contacts (subscribe [:all-added-dapps])] (let [contacts (subscribe [:all-added-contacts])]
(reaction (take limit @contacts))))) (reaction (take limit @contacts)))))
(register-sub :added-people-count (register-sub :added-contacts-count
(fn [_ _] (fn [_ _]
(let [contacts (subscribe [:all-added-people])] (let [contacts (subscribe [:all-added-contacts])]
(reaction (count @contacts))))) (reaction (count @contacts)))))
(register-sub :added-dapps-count (register-sub :all-added-groups
(fn [_ _] (fn [db _]
(let [contacts (subscribe [:all-added-dapps])] (let [groups (reaction (vals (:contact-groups @db)))]
(reaction (count @contacts))))) (->> (remove :pending? @groups)
(sort-by :order >)
(reaction)))))
(defn get-contact-letter [contact] (defn get-contact-letter [contact]
(when-let [letter (first (:name contact))] (when-let [letter (first (:name contact))]
(clojure.string/upper-case letter))) (clojure.string/upper-case letter)))
(defn search-filter [text item]
(let [name (-> (or (:name item) "")
(str/lower-case))
text (str/lower-case text)]
(not= (str/index-of name text) nil)))
(register-sub :all-added-group-contacts-filtered
(fn [_ [_ group-id]]
(let [contacts (if group-id
(subscribe [:all-added-group-contacts group-id])
(subscribe [:all-added-contacts]))
text (subscribe [:get-in [:toolbar-search :text]])]
(reaction
(if @text
(filter #(search-filter @text %) @contacts)
@contacts)))))
(register-sub :contacts-with-letters (register-sub :contacts-with-letters
(fn [db _] (fn [db _]
(let [contacts (reaction (:contacts @db)) (let [contacts (reaction (:contacts @db))]
pred (subscribe [:get :contacts-filter])]
(reaction (reaction
(let [ordered (sort-contacts @contacts) (let [ordered (sort-contacts @contacts)]
ordered (if @pred (filter @pred ordered) ordered)]
(reduce (fn [prev cur] (reduce (fn [prev cur]
(let [prev-letter (get-contact-letter (last prev)) (let [prev-letter (get-contact-letter (last prev))
cur-letter (get-contact-letter cur)] cur-letter (get-contact-letter cur)]

View File

@ -4,31 +4,40 @@
[re-frame.core :refer [dispatch]] [re-frame.core :refer [dispatch]]
[status-im.contacts.styles :as st] [status-im.contacts.styles :as st]
[status-im.contacts.views.contact-inner :refer [contact-inner-view]] [status-im.contacts.views.contact-inner :refer [contact-inner-view]]
[status-im.components.context-menu :refer [context-menu]]
[status-im.i18n :refer [label]]
[status-im.utils.platform :refer [platform-specific]])) [status-im.utils.platform :refer [platform-specific]]))
(defn- on-press [{:keys [whisper-identity] :as contact}] (defn- on-press [{:keys [whisper-identity] :as contact}]
(dispatch [:send-contact-request! contact]) (dispatch [:send-contact-request! contact])
(dispatch [:start-chat whisper-identity {} :navigation-replace])) (dispatch [:start-chat whisper-identity {} :navigation-replace]))
(defn- more-on-press [contact]
(dispatch [:open-contact-menu (:list-selection-fn platform-specific) contact]))
(defn letter-view [letter] (defn letter-view [letter]
[view st/letter-container [view st/letter-container
(when letter (when letter
[text {:style st/letter-text} letter])]) [text {:style st/letter-text} letter])])
(defn options-btn [contact more-options]
(let [options [{:value #(dispatch [:hide-contact contact]) :text (label :t/delete-contact)}]]
[view st/more-btn
[context-menu
[icon :options_gray]
(or more-options options)]]))
;;TODO: maybe it's better to have only one global component contact-view with the types: default, extended and toggle
;;TODO: at the moment toggle in the other component new-group-contact
(defview contact-view [{{:keys [whisper-identity letter dapp?] :as contact} :contact (defview contact-view [{{:keys [whisper-identity letter dapp?] :as contact} :contact
:keys [extended? letter? on-click more-on-click info]}] :keys [extended? letter? on-click extend-options info]}]
[chat [:get-chat whisper-identity]] [chat [:get-chat whisper-identity]]
[touchable-highlight [touchable-highlight
{:on-press #((or on-click on-press) contact)} (when-not extended?
[view st/contact-container {:on-press #((or on-click on-press) contact)})
(when letter? [view
[letter-view letter]) [view st/contact-container
[contact-inner-view contact info] (when letter?
(when (and extended? (not dapp?)) [letter-view letter])
[touchable-highlight [contact-inner-view {:contact contact :info info}]
{:on-press #((or more-on-click more-on-press) contact)} (when extended?
[view st/more-btn [options-btn contact extend-options])]
[icon :more_vertical st/more-btn-icon]]])]]) [view st/contact-separator-container
[view (get-in platform-specific [:component-styles :contacts :separator])]]]])

View File

@ -10,10 +10,8 @@
[contact-icon-contacts-tab contact]]) [contact-icon-contacts-tab contact]])
(defn contact-inner-view (defn contact-inner-view
([contact] ([{:keys [info style] {:keys [whisper-identity name] :as contact} :contact}]
(contact-inner-view contact nil)) [view (merge st/contact-inner-container style)
([{:keys [whisper-identity name] :as contact} info]
[view st/contact-inner-container
[contact-photo contact] [contact-photo contact]
[view st/info-container [view st/info-container
[text {:style st/name-text [text {:style st/name-text

View File

@ -9,7 +9,7 @@
[status-im.contacts.views.contact :refer [contact-view]] [status-im.contacts.views.contact :refer [contact-view]]
[status-im.components.text-field.view :refer [text-field]] [status-im.components.text-field.view :refer [text-field]]
[status-im.components.status-bar :refer [status-bar]] [status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar]] [status-im.components.toolbar.view :refer [toolbar-with-search toolbar]]
[status-im.components.toolbar.actions :as act] [status-im.components.toolbar.actions :as act]
[status-im.components.toolbar.styles :refer [toolbar-background1]] [status-im.components.toolbar.styles :refer [toolbar-background1]]
[status-im.components.drawer.view :refer [drawer-view open-drawer]] [status-im.components.drawer.view :refer [drawer-view open-drawer]]
@ -17,8 +17,7 @@
[status-im.contacts.styles :as st] [status-im.contacts.styles :as st]
[status-im.utils.listview :as lw] [status-im.utils.listview :as lw]
[status-im.i18n :refer [label]] [status-im.i18n :refer [label]]
[status-im.utils.platform :refer [platform-specific]] [status-im.utils.platform :refer [platform-specific]]))
[status-im.contacts.views.contact-inner :refer [contact-inner-view]]))
(defn new-group-chat-view [] (defn new-group-chat-view []
[view [view
@ -46,6 +45,7 @@
(defn render-row [chat-modal click-handler action params] (defn render-row [chat-modal click-handler action params]
(fn [row _ _] (fn [row _ _]
(list-item (list-item
^{:key row}
[contact-view {:contact row [contact-view {:contact row
:letter? chat-modal :letter? chat-modal
:on-click (when click-handler :on-click (when click-handler
@ -64,58 +64,62 @@
label]]]]]) label]]]]])
(defview contact-list-toolbar [] (defview contact-list-toolbar []
[group [:get :contacts-group] [group [:get :contacts-group]
modal [:get :modal]] modal [:get :modal]
show-search [:get-in [:toolbar-search :show]]]
[view [view
[status-bar] [status-bar]
[toolbar {:title (label (if-not group (toolbar-with-search
:t/contacts {:show-search? (= show-search :contact-list)
(if (= group :dapps) :search-key :contact-list
:t/contacts-group-dapps :title (if-not group
:t/contacts-group-new-chat))) (label :t/contacts)
:nav-action (when modal (or (:name group) (label :t/contacts-group-new-chat)))
(act/back #(dispatch [:navigate-back]))) :search-placeholder (label :t/search-for)
:background-color toolbar-background1 :actions (when modal
:style (get-in platform-specific [:component-styles :toolbar]) (act/back #(dispatch [:navigate-back])))})])
:actions [(act/search #())]}]])
(defview contact-list [] (defview contacts-list-view [group modal click-handler action]
[contacts [:contacts-with-letters] [contacts [:all-added-group-contacts-filtered (:group-id group)]
group [:get :contacts-group]
modal [:get :modal]
click-handler [:get :contacts-click-handler]
action [:get :contacts-click-action]
params [:get :contacts-click-params]] params [:get :contacts-click-params]]
(let [show-new-group-chat? (and (= group :people) (let [show-new-group-chat? (and (= group :people)
(get-in platform-specific [:chats :new-chat-in-toolbar?]))] (get-in platform-specific [:chats :new-chat-in-toolbar?]))]
[drawer-view (when contacts
[view st/contacts-list-container [list-view {:dataSource (lw/to-datasource contacts)
[contact-list-toolbar] :enableEmptySections true
;; todo add stub :renderRow (render-row modal click-handler action params)
(when modal :bounces false
[view :keyboardShouldPersistTaps true
[contact-list-entry {:click-handler #(do :renderHeader #(list-item
(dispatch [:send-to-webview-bridge [view
{:event (name :webview-send-transaction)}]) (if show-new-group-chat?
(dispatch [:navigate-back])) [new-group-chat-view])
:icon :icon_enter_address [view st/spacing-top]])
:icon-style st/enter-address-icon :renderFooter #(list-item [view st/spacing-bottom])
:label (label :t/enter-address)}] :style st/contacts-list}])))
[contact-list-entry {:click-handler #(click-handler :qr-scan action)
:icon :icon_scan_q_r (defview contact-list []
:icon-style st/scan-qr-icon [action [:get :contacts-click-action]
:label (label (if (= :request action) modal [:get :modal]
:t/show-qr click-handler [:get :contacts-click-handler]
:t/scan-qr))}]]) group [:get :contacts-group]]
(when contacts [drawer-view
[list-view {:dataSource (lw/to-datasource contacts) [view st/contacts-list-container
:enableEmptySections true [contact-list-toolbar]
:renderRow (render-row modal click-handler action params) ;; todo add stub
:bounces false (when modal
:renderHeader #(list-item [view
[view [contact-list-entry {:click-handler #(do
(if show-new-group-chat? (dispatch [:send-to-webview-bridge
[new-group-chat-view]) {:event (name :webview-send-transaction)}])
[view st/spacing-top]]) (dispatch [:navigate-back]))
:renderFooter #(list-item [view st/spacing-bottom]) :icon :icon_enter_address
:style st/contacts-list}])]])) :icon-style st/enter-address-icon
:label (label :t/enter-address)}]
[contact-list-entry {:click-handler #(click-handler :qr-scan action)
:icon :icon_scan_q_r
:icon-style st/scan-qr-icon
:label (label (if (= :request action)
:t/show-qr
:t/scan-qr))}]])
[contacts-list-view group modal click-handler action]]])

View File

@ -4,12 +4,12 @@
(:refer-clojure :exclude [exists?])) (:refer-clojure :exclude [exists?]))
(defn- normalize-contacts (defn- normalize-contacts
[chats] [item]
(map #(update % :contacts vals) chats)) (update item :contacts vals))
(defn get-all (defn get-all
[] []
(normalize-contacts (data-store/get-all-active))) (map normalize-contacts (data-store/get-all-active)))
(defn get-by-id (defn get-by-id
[id] [id]

View File

@ -0,0 +1,23 @@
(ns status-im.data-store.contact-groups
(:require [status-im.data-store.realm.contact-groups :as data-store])
(:refer-clojure :exclude [exists?]))
(defn- normalize-contacts
[item]
(update item :contacts vals))
(defn get-all
[]
(map normalize-contacts (data-store/get-all-as-list)))
(defn save
[{:keys [group-id] :as group}]
(data-store/save group (data-store/exists? group-id)))
(defn save-all
[groups]
(mapv save groups))
(defn delete
[group-id]
(data-store/delete group-id))

View File

@ -0,0 +1,26 @@
(ns status-im.data-store.realm.contact-groups
(:require [status-im.data-store.realm.core :as realm]
[status-im.utils.random :refer [timestamp]])
(:refer-clojure :exclude [exists?]))
(defn get-all
[]
(-> @realm/account-realm
(realm/get-all :contact-group)))
(defn get-all-as-list
[]
(realm/realm-collection->list (get-all)))
(defn save
[group update?]
(realm/save @realm/account-realm :contact-group group update?))
(defn exists?
[group-id]
(realm/exists? @realm/account-realm :contact-group {:group-id group-id}))
(defn delete
[group-id]
(when-let [group (realm/get-one-by-field @realm/account-realm :contact-group :group-id group-id)]
(realm/delete @realm/account-realm group)))

View File

@ -3,7 +3,8 @@
[status-im.data-store.realm.schemas.account.v2.core :as v2] [status-im.data-store.realm.schemas.account.v2.core :as v2]
[status-im.data-store.realm.schemas.account.v3.core :as v3] [status-im.data-store.realm.schemas.account.v3.core :as v3]
[status-im.data-store.realm.schemas.account.v4.core :as v4] [status-im.data-store.realm.schemas.account.v4.core :as v4]
)) [status-im.data-store.realm.schemas.account.v5.core :as v5]))
; put schemas ordered by version ; put schemas ordered by version
(def schemas [{:schema v1/schema (def schemas [{:schema v1/schema
@ -17,4 +18,7 @@
:migration v3/migration} :migration v3/migration}
{:schema v4/schema {:schema v4/schema
:schemaVersion 4 :schemaVersion 4
:migration v4/migration}]) :migration v4/migration}
{:schema v5/schema
:schemaVersion 5
:migration v5/migration}])

View File

@ -29,4 +29,4 @@
(defn migration [old-realm new-realm] (defn migration [old-realm new-realm]
(log/debug "migrating v2 account database: " old-realm new-realm) (log/debug "migrating v2 account database: " old-realm new-realm)
(chat/migration old-realm new-realm) (chat/migration old-realm new-realm)
(contact/migration old-realm new-realm)) (contact/migration old-realm new-realm))

View File

@ -0,0 +1,15 @@
(ns status-im.data-store.realm.schemas.account.v5.contact-group
(:require [taoensso.timbre :as log]))
(def schema {:name :contact-group
:primaryKey :group-id
:properties {:group-id :string
:name :string
:timestamp :int
:order :int
:pending? {:type :bool :default false}
:contacts {:type :list
:objectType :group-contact}}})
(defn migration [old-realm new-realm]
(log/debug "migrating group schema v5"))

View File

@ -0,0 +1,36 @@
(ns status-im.data-store.realm.schemas.account.v5.core
(:require [status-im.data-store.realm.schemas.account.v4.chat :as chat]
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
[status-im.data-store.realm.schemas.account.v1.command :as command]
[status-im.data-store.realm.schemas.account.v3.contact :as contact]
[status-im.data-store.realm.schemas.account.v1.discover :as discover]
[status-im.data-store.realm.schemas.account.v1.kv-store :as kv-store]
[status-im.data-store.realm.schemas.account.v4.message :as message]
[status-im.data-store.realm.schemas.account.v1.pending-message :as pending-message]
[status-im.data-store.realm.schemas.account.v1.processed-message :as processed-message]
[status-im.data-store.realm.schemas.account.v1.request :as request]
[status-im.data-store.realm.schemas.account.v1.tag :as tag]
[status-im.data-store.realm.schemas.account.v1.user-status :as user-status]
[status-im.data-store.realm.schemas.account.v5.contact-group :as contact-group]
[status-im.data-store.realm.schemas.account.v5.group-contact :as group-contact]
[taoensso.timbre :as log]))
(def schema [chat/schema
chat-contact/schema
command/schema
contact/schema
discover/schema
kv-store/schema
message/schema
pending-message/schema
processed-message/schema
request/schema
tag/schema
user-status/schema
contact-group/schema
group-contact/schema])
(defn migration [old-realm new-realm]
(log/debug "migrating v5 account database: " old-realm new-realm)
(chat/migration old-realm new-realm)
(contact/migration old-realm new-realm))

View File

@ -0,0 +1,8 @@
(ns status-im.data-store.realm.schemas.account.v5.group-contact
(:require [taoensso.timbre :as log]))
(def schema {:name :group-contact
:properties {:identity "string"}})
(defn migration [_ _]
(log/debug "migrating group-contact schema v5"))

View File

@ -20,6 +20,7 @@
:new-contact-identity "" :new-contact-identity ""
:contacts {} :contacts {}
:contact-groups {}
:discoveries {} :discoveries {}
:discover-search-tags [] :discover-search-tags []
:tags {} :tags {}

View File

@ -74,6 +74,7 @@
(dispatch [:initialize-sync-listener]) (dispatch [:initialize-sync-listener])
(dispatch [:initialize-chats]) (dispatch [:initialize-chats])
(dispatch [:load-contacts]) (dispatch [:load-contacts])
(dispatch [:load-groups])
(dispatch [:init-chat]) (dispatch [:init-chat])
(dispatch [:init-discoveries]) (dispatch [:init-discoveries])
(dispatch [:init-debug-mode address]) (dispatch [:init-debug-mode address])

View File

@ -22,7 +22,8 @@
[status-im.accounts.screen :refer [accounts]] [status-im.accounts.screen :refer [accounts]]
[status-im.transactions.screen :refer [confirm]] [status-im.transactions.screen :refer [confirm]]
[status-im.chats-list.screen :refer [chats-list]] [status-im.chats-list.screen :refer [chats-list]]
[status-im.new-group.screen-private :refer [new-group]] [status-im.new-group.screen-private :refer [new-group contact-group]]
[status-im.new-group.views.contact-list :refer [contact-group-list]]
[status-im.new-group.screen-public :refer [new-public-group]] [status-im.new-group.screen-public :refer [new-public-group]]
[status-im.participants.views.add :refer [new-participants]] [status-im.participants.views.add :refer [new-participants]]
[status-im.participants.views.remove :refer [remove-participants]] [status-im.participants.views.remove :refer [remove-participants]]
@ -92,6 +93,8 @@
:contact-list main-tabs :contact-list main-tabs
:contact-list-search-results contacts-search-results :contact-list-search-results contacts-search-results
:group-contacts contact-list :group-contacts contact-list
:contact-group contact-group
:contact-group-list contact-group-list
:new-contact new-contact :new-contact new-contact
:qr-scanner qr-scanner :qr-scanner qr-scanner
:chat chat :chat chat

View File

@ -1,6 +1,5 @@
(ns status-im.ios.platform (ns status-im.ios.platform
(:require [status-im.components.styles :as styles] (:require [status-im.components.styles :as styles]))
[status-im.components.toolbar.styles :refer [toolbar-background2]]))
(def component-styles (def component-styles
{:status-bar {:default {:height 20 {:status-bar {:default {:height 20
@ -8,7 +7,7 @@
:color styles/color-white} :color styles/color-white}
:main {:height 20 :main {:height 20
:bar-style "default" :bar-style "default"
:color toolbar-background2} :color styles/color-white}
:transparent {:height 20 :transparent {:height 20
:bar-style "light-content" :bar-style "light-content"
:color styles/color-transparent} :color styles/color-transparent}
@ -23,7 +22,7 @@
:border-bottom-width 0.5} :border-bottom-width 0.5}
:chat {:new-message {:border-top-color styles/color-gray3 :chat {:new-message {:border-top-color styles/color-gray3
:border-top-width 0.5}} :border-top-width 0.5}}
:discover {:subtitle {:color styles/color-steel :discover {:subtitle {:color styles/color-steel
:font-size 13 :font-size 13
:letter-spacing 1} :letter-spacing 1}
:popular {:border-radius 3 :popular {:border-radius 3
@ -41,9 +40,21 @@
:icon {:padding-top 0 :icon {:padding-top 0
:bottom -4 :bottom -4
:justify-content :flex-end}}} :justify-content :flex-end}}}
:contacts {:subtitle {:color styles/color-steel :contacts {:subtitle {:color styles/color-black
:font-size 13 :font-size 16
:letter-spacing 1}} :letter-spacing -0.2}
:separator {:margin-left 68
:height 1
:background-color styles/color-gray5
:opacity 0.4}
:icon-check {:border-radius 50
:width 24
:height 24}
:group-header {:flexDirection :row
:alignItems :center
:margin-top 24
:height 53
:backgroundColor styles/color-white}}
:bottom-gradient {:height 1} :bottom-gradient {:height 1}
:input-label {:left 0} :input-label {:left 0}
:input-error-text {:margin-left 0} :input-error-text {:margin-left 0}
@ -55,7 +66,8 @@
:toolbar-last-activity {:color styles/text2-color :toolbar-last-activity {:color styles/text2-color
:background-color :transparent :background-color :transparent
:top 0 :top 0
:font-size 14}}) :font-size 14}
:toolbar-title-container {:align-items :center}})
(def fonts (def fonts
{:light {:font-family "SFUIText-Light"} {:light {:font-family "SFUIText-Light"}
@ -75,7 +87,6 @@
:cancelButtonIndex (count options)}) :cancelButtonIndex (count options)})
callback)) callback))
;; Structure to be exported ;; Structure to be exported
(def platform-specific (def platform-specific

View File

@ -4,7 +4,9 @@
[status-im.utils.handlers :refer [register-handler]] [status-im.utils.handlers :refer [register-handler]]
[status-im.components.styles :refer [default-chat-color]] [status-im.components.styles :refer [default-chat-color]]
[status-im.data-store.chats :as chats] [status-im.data-store.chats :as chats]
[status-im.data-store.contact-groups :as groups]
[clojure.string :as s] [clojure.string :as s]
[status-im.i18n :refer [label]]
[status-im.utils.handlers :as u] [status-im.utils.handlers :as u]
[status-im.utils.random :as random] [status-im.utils.random :as random]
[taoensso.timbre :refer-macros [debug]] [taoensso.timbre :refer-macros [debug]]
@ -86,7 +88,7 @@
:private private-key} :private private-key}
:callback #(dispatch [:incoming-message %1 %2])}))) :callback #(dispatch [:incoming-message %1 %2])})))
(register-handler :create-new-group (register-handler :create-new-group-chat
(-> prepare-chat (-> prepare-chat
((enrich add-chat)) ((enrich add-chat))
((after create-chat!)) ((after create-chat!))
@ -161,6 +163,98 @@
:keypair keypair :keypair keypair
:callback #(dispatch [:incoming-message %1 %2])}))))))) :callback #(dispatch [:incoming-message %1 %2])})))))))
(defn prepare-group
[{:keys [selected-contacts contact-groups] :as db} [_ group-name]]
(let [contacts (mapv #(hash-map :identity %) selected-contacts)]
(assoc db :new-group {:group-id (random/id)
:name group-name
:order (count contact-groups)
:timestamp (random/timestamp)
:contacts contacts})))
(defn add-group
[{:keys [new-group] :as db} _]
(update db :contact-groups merge {(:group-id new-group) new-group}))
(defn update-group
[{:keys [new-group] :as db} _]
(update db :contact-groups merge {(:group-id new-group) new-group}))
(defn create-group!
[{:keys [new-group]} _]
(groups/save new-group))
(defn update-group!
[{:keys [new-group] :as db} _]
(groups/save new-group))
(defn show-contact-list!
[_ _]
(dispatch [:navigate-to-clean :contact-list]))
(register-handler
:create-new-group
(-> prepare-group
((enrich add-group))
((after create-group!))
((after show-contact-list!))))
(defn prepare-group-after-edit
[{:keys [selected-contacts] :as db} [_ group group-name]]
(let [contacts (mapv #(hash-map :identity %) selected-contacts)
group' (assoc group :name group-name
:contacts contacts)]
(assoc db :new-group group')))
(register-handler
:update-group-after-edit
(-> prepare-group-after-edit
((enrich update-group))
((after update-group!))
((after show-contact-list!))))
(register-handler
:update-group
(-> (fn [db [_ new-group]]
(assoc db :new-group new-group))
((enrich update-group))
((after update-group!))
((after show-contact-list!))))
(defn save-groups! [{:keys [new-groups]} _]
(groups/save-all new-groups))
(defn update-pending-status [old-groups {:keys [group-id pending?] :as group}]
(let [{old-pending :pending?
:as old-group} (get old-groups group-id)
pending?' (if old-pending (and old-pending pending?) pending?)]
(assoc group :pending? (boolean pending?'))))
(defn add-new-groups
[{:keys [contact-groups] :as db} [_ new-groups]]
(let [identities (set (keys contact-groups))
new-groups' (->> new-groups
(map #(update-pending-status contact-groups %))
(remove #(identities (:group-id %)))
(map #(vector (:group-id %) %))
(into {}))]
(-> db
(update :contact-groups merge new-groups')
(assoc :new-groups (vals new-groups')))))
(register-handler :add-groups
(after save-groups!)
add-new-groups)
(defn load-groups! [db _]
(let [groups (->> (groups/get-all)
(map (fn [{:keys [group-id] :as group}]
[group-id group]))
(into {}))]
(assoc db :contact-groups groups)))
(register-handler :load-groups load-groups!)
(defmethod nav/preload-data! :new-public-group (defmethod nav/preload-data! :new-public-group
[db] [db]
(dissoc db :public-group/topic)) (dissoc db :public-group/topic))

View File

@ -2,6 +2,7 @@
(:require-macros [status-im.utils.views :refer [defview]]) (:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch]] (:require [re-frame.core :refer [subscribe dispatch]]
[status-im.resources :as res] [status-im.resources :as res]
[status-im.contacts.views.contact :refer [contact-view]]
[status-im.components.react :refer [view [status-im.components.react :refer [view
text text
image image
@ -10,10 +11,12 @@
list-view list-view
list-item]] list-item]]
[status-im.components.text-field.view :refer [text-field]] [status-im.components.text-field.view :refer [text-field]]
[status-im.components.confirm-button :refer [confirm-button]]
[status-im.components.styles :refer [color-blue [status-im.components.styles :refer [color-blue
separator-color]] separator-color]]
[status-im.components.status-bar :refer [status-bar]] [status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar]] [status-im.components.toolbar.view :refer [toolbar-with-search toolbar]]
[status-im.utils.platform :refer [platform-specific]]
[status-im.utils.listview :refer [to-datasource]] [status-im.utils.listview :refer [to-datasource]]
[status-im.new-group.views.contact :refer [new-group-contact]] [status-im.new-group.views.contact :refer [new-group-contact]]
[status-im.new-group.styles :as st] [status-im.new-group.styles :as st]
@ -21,7 +24,7 @@
[status-im.i18n :refer [label]] [status-im.i18n :refer [label]]
[cljs.spec :as s])) [cljs.spec :as s]))
(defview new-group-toolbar [] (defview new-chat-group-toolbar []
[new-chat-name [:get :new-chat-name]] [new-chat-name [:get :new-chat-name]]
(let [create-btn-enabled? (s/valid? ::v/name new-chat-name)] (let [create-btn-enabled? (s/valid? ::v/name new-chat-name)]
[view [view
@ -31,15 +34,13 @@
:actions [{:image {:source res/v ;; {:uri "icon_search"} :actions [{:image {:source res/v ;; {:uri "icon_search"}
:style (st/toolbar-icon create-btn-enabled?)} :style (st/toolbar-icon create-btn-enabled?)}
:handler (when create-btn-enabled? :handler (when create-btn-enabled?
#(dispatch [:create-new-group new-chat-name]))}]}]])) #(dispatch [:create-new-group-chat new-chat-name]))}]}]]))
(defview group-name-input [] (defview group-name-input []
[new-chat-name [:get :new-chat-name]] [new-chat-name [:get :new-chat-name]]
[view [view
[text-field [text-field
{:error (cond {:error (when
(not (s/valid? ::v/not-empty-string new-chat-name))
(label :t/empty-group-chat-name)
(not (s/valid? ::v/not-illegal-name new-chat-name)) (not (s/valid? ::v/not-illegal-name new-chat-name))
(label :t/illegal-group-chat-name)) (label :t/illegal-group-chat-name))
:wrapper-style st/group-chat-name-wrapper :wrapper-style st/group-chat-name-wrapper
@ -54,7 +55,7 @@
(defview new-group [] (defview new-group []
[contacts [:all-added-contacts]] [contacts [:all-added-contacts]]
[view st/new-group-container [view st/new-group-container
[new-group-toolbar] [new-chat-group-toolbar]
[view st/chat-name-container [view st/chat-name-container
[text {:style st/members-text [text {:style st/members-text
:font :medium} :font :medium}
@ -64,11 +65,64 @@
:font :medium} :font :medium}
(label :t/members-title)] (label :t/members-title)]
#_[touchable-highlight {:on-press (fn [])} #_[touchable-highlight {:on-press (fn [])}
[view st/add-container [view st/add-container
[icon :add_gray st/add-icon] [icon :add_gray st/add-icon]
[text {:style st/add-text} (label :t/add-members)]]] [text {:style st/add-text} (label :t/add-members)]]]
[list-view [list-view
{:dataSource (to-datasource contacts) {:dataSource (to-datasource contacts)
:renderRow (fn [row _ _] :renderRow (fn [row _ _]
(list-item [new-group-contact row])) (list-item [new-group-contact row]))
:style st/contacts-list}]]]) :style st/contacts-list}]]])
(defview new-contacts-group-toolbar [edit?]
[view
[status-bar]
[toolbar
{:title (label (if edit? :t/edit-group :t/new-group))}]])
(defn chat-name-view [contacts-count]
[view st/chat-name-container
[text {:style st/group-name-text
:font :medium}
(label :t/group-name)]
[group-name-input]
[text {:style st/members-text
:font :medium}
(str (label :t/group-members) " " contacts-count)]
[touchable-highlight {:on-press #(dispatch [:navigate-forget :contact-group-list])}
[view st/add-container
[icon :add_blue st/add-icon]
[text {:style st/add-text} (label :t/add-members)]]]])
(defn delete-btn [on-press]
[touchable-highlight {:on-press on-press}
[view st/delete-group-container
[text {:style st/delete-group-text} (label :t/delete-group)]
[text {:style st/delete-group-prompt-text} (label :t/delete-group-prompt)]]])
;;TODO: should be refactored into one common function for group chats and contact groups
(defview contact-group []
[contacts [:selected-group-contacts]
group-name [:get :new-chat-name]
group [:get :contact-group]]
(let [save-btn-enabled? (and (s/valid? ::v/name group-name) (pos? (count contacts)))]
[view st/new-group-container
[new-contacts-group-toolbar (boolean group)]
[chat-name-view (count contacts)]
[list-view
{:dataSource (to-datasource contacts)
:renderRow (fn [row _ _]
(list-item
^{:key row}
[contact-view
{:contact row
:extend-options [{:value #(dispatch [:deselect-contact (:whisper-identity row)])
:text (label :t/remove-from-group)}]
:extended? true}]))
:style st/contacts-list}]
(when group
[delete-btn #(dispatch [:update-group (assoc group :pending? true)])])
(when save-btn-enabled?
[confirm-button (label :t/save) (if group
#(dispatch [:update-group-after-edit group group-name])
#(dispatch [:create-new-group group-name]))])]))

View File

@ -2,7 +2,11 @@
(:require [status-im.components.styles :refer [color-white (:require [status-im.components.styles :refer [color-white
color-blue color-blue
text1-color text1-color
text2-color]] text2-color
color-light-blue
color-light-red
selected-contact-color
color-gray4]]
[status-im.utils.platform :refer [platform-specific]])) [status-im.utils.platform :refer [platform-specific]]))
(defn toolbar-icon [enabled?] (defn toolbar-icon [enabled?]
@ -37,27 +41,49 @@
(def group-chat-name-wrapper (def group-chat-name-wrapper
{:padding-top 0}) {:padding-top 0})
(def group-name-text
{:margin-top 11
:margin-bottom 10
:letter-spacing -0.1
:color color-gray4
:font-size 13
:line-height 20})
(def members-text (def members-text
{:margin-top 24 {:margin-top 10
:margin-bottom 8 :margin-bottom 8
:color text2-color :letter-spacing -0.2
:font-size 14 :color color-gray4
:line-height 20}) :font-size 16
:line-height 19})
(def add-container (def add-container
{:flex-direction :row {:flex-direction :row
:margin-bottom 16}) :align-items :center
:margin-top 16
:margin-bottom 16
:margin-right 20})
(def add-icon (def add-icon
{:margin-vertical 18 {:align-items :center
:margin-horizontal 3 :width 24
:width 17 :height 24})
:height 17})
(def add-text (def add-text
{:margin-top 16 {:margin-left 32
:margin-left 32 :color color-light-blue
:color text2-color :letter-spacing -0.2
:font-size 17
:line-height 20})
(def delete-group-text
{:color color-light-red
:letter-spacing 0.5
:font-size 14
:line-height 20})
(def delete-group-prompt-text
{:color color-gray4
:font-size 14 :font-size 14
:line-height 20}) :line-height 20})
@ -70,9 +96,25 @@
:align-items :center :align-items :center
:height 56}) :height 56})
(def contact-item-checkbox (def selected-contact
{:outer-size 20 {:background-color selected-contact-color})
:filter-size 16
:inner-size 12 (def icon-check-container
:outer-color color-blue (merge (get-in platform-specific [:component-styles :contacts :icon-check])
:inner-color color-blue}) {:alignItems :center
:justifyContent :center}))
(def toggle-container
{:width 56
:height 56
:alignItems :center
:justifyContent :center})
(def check-icon
{:width 12
:height 12})
(def delete-group-container
{:height 56
:padding-left 72
:margin-top 15})

View File

@ -1,7 +1,19 @@
(ns status-im.new-group.subs (ns status-im.new-group.subs
(:require-macros [reagent.ratom :refer [reaction]]) (:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub]] (:require [re-frame.core :refer [register-sub subscribe]]
[status-im.utils.subs :as u])) [status-im.utils.subs :as u]))
(register-sub :is-contact-selected? (register-sub :is-contact-selected?
(u/contains-sub :selected-contacts)) (u/contains-sub :selected-contacts))
(register-sub :selected-contacts-count
(fn [_ _]
(let [contacts (subscribe [:get :selected-contacts])]
(reaction (count @contacts)))))
(register-sub :selected-group-contacts
(fn [_ _]
(let [selected-contacts (subscribe [:get :selected-contacts])
added-contacts (subscribe [:all-added-contacts])]
(reaction (do @selected-contacts ;;TODO: doesn't work without this line :(
(filter #(@selected-contacts (:whisper-identity %)) @added-contacts))))))

View File

@ -1,20 +1,29 @@
(ns status-im.new-group.views.contact (ns status-im.new-group.views.contact
(:require-macros [status-im.utils.views :refer [defview]]) (:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[status-im.components.react :refer [view]] [status-im.components.react :refer [view icon touchable-highlight]]
[status-im.contacts.views.contact-inner :refer [contact-inner-view]] [status-im.contacts.views.contact-inner :refer [contact-inner-view]]
[status-im.components.item-checkbox :refer [item-checkbox]] [status-im.new-group.styles :as st]
[status-im.new-group.styles :as st])) [status-im.contacts.styles :as cst]
[status-im.components.styles :refer [color-light-blue color-gray5]]
[status-im.utils.platform :refer [platform-specific]]))
(defn on-toggle [whisper-identity] (defn on-toggle [checked? whisper-identity]
(fn [checked?] (let [action (if checked? :deselect-contact :select-contact)]
(let [action (if checked? :select-contact :deselect-contact)] (dispatch [action whisper-identity])))
(dispatch [action whisper-identity]))))
;;TODO: maybe it's better to have only one global component contact-view (with the types: default, extended and toggle)
(defview new-group-contact [{:keys [whisper-identity] :as contact}] (defview new-group-contact [{:keys [whisper-identity] :as contact}]
[checked [:is-contact-selected? whisper-identity]] [checked [:is-contact-selected? whisper-identity]]
[view st/contact-container [touchable-highlight {:on-press #(on-toggle checked whisper-identity)}
[item-checkbox (merge {:on-toggle (on-toggle whisper-identity) [view
:checked checked} [view (merge st/contact-container (when checked {:style st/selected-contact}))
st/contact-item-checkbox)] [contact-inner-view (merge {:contact contact}
[contact-inner-view contact]]) (when checked {:style st/selected-contact}))]
[view st/toggle-container
[view (merge st/icon-check-container
{:background-color (if checked color-light-blue color-gray5)})
(when checked
[icon :check_on st/check-icon])]]]
[view cst/contact-separator-container
[view (get-in platform-specific [:component-styles :contacts :separator])]]]])

View File

@ -0,0 +1,38 @@
(ns status-im.new-group.views.contact-list
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.react :refer [view
text
list-view
list-item]]
[status-im.components.confirm-button :refer [confirm-button]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar-with-search]]
[status-im.utils.listview :refer [to-datasource]]
[status-im.new-group.views.contact :refer [new-group-contact]]
[status-im.new-group.styles :as st]
[status-im.i18n :refer [label]]))
(defn contact-list-toolbar [contacts-count show-search?]
(toolbar-with-search
{:show-search? (= show-search? :contact-group-list)
:search-key :contact-group-list
:title (str (label :t/new-group) " (" contacts-count ")")
:search-placeholder (label :t/search-for)}))
(defview contact-group-list []
[contacts [:all-added-group-contacts-filtered]
selected-contacts-count [:selected-contacts-count]
show-search [:get-in [:toolbar-search :show]]]
[view st/new-group-container
[status-bar]
[contact-list-toolbar selected-contacts-count show-search]
[view {:flex 1}
[list-view
{:dataSource (to-datasource contacts)
:renderRow (fn [row _ _]
(list-item ^{:key row} [new-group-contact row]))
:style st/contacts-list
:keyboardShouldPersistTaps true}]]
(when (pos? selected-contacts-count)
[confirm-button (label :t/next) #(dispatch [:navigation-replace :contact-group])])])

View File

@ -19,4 +19,4 @@
[item-checkbox {:onToggle (on-toggle whisper-identity) [item-checkbox {:onToggle (on-toggle whisper-identity)
:checked checked :checked checked
:size 30}] :size 30}]
[contact-inner-view contact]]) [contact-inner-view {:contact contact}]])

View File

@ -10,6 +10,7 @@
:offline "Offline" :offline "Offline"
:search-for "Search for..." :search-for "Search for..."
:cancel "Cancel" :cancel "Cancel"
:next "Next"
;drawer ;drawer
:invite-friends "Invite friends" :invite-friends "Invite friends"
@ -141,8 +142,10 @@
;contacts ;contacts
:contacts "Contacts" :contacts "Contacts"
:new-contact "New Contact" :new-contact "New contact"
:remove-contact "Remove contact" :delete-contact "Delete contact"
:remove-from-group "Remove from group"
:edit-contacts "Edit contacts"
:show-all "SHOW ALL" :show-all "SHOW ALL"
:contacts-group-dapps "ÐApps" :contacts-group-dapps "ÐApps"
:contacts-group-people "People" :contacts-group-people "People"
@ -150,6 +153,7 @@
:no-contacts "No contacts yet" :no-contacts "No contacts yet"
:show-qr "Show QR" :show-qr "Show QR"
:enter-address "Enter address" :enter-address "Enter address"
:more "more"
;group-settings ;group-settings
:remove "Remove" :remove "Remove"
@ -185,6 +189,13 @@
:group-chat-name "Chat name" :group-chat-name "Chat name"
:empty-group-chat-name "Please enter a name" :empty-group-chat-name "Please enter a name"
:illegal-group-chat-name "Please select another name" :illegal-group-chat-name "Please select another name"
:new-group "New Group"
:reorder-groups "Reorder Group"
:group-name "Group name"
:edit-group "Edit Group"
:delete-group "DELETE GROUP"
:delete-group-prompt "This will not affect group members"
:group-members "Group members"
;participants ;participants
:add-participants "Add Participants" :add-participants "Add Participants"

View File

@ -8,6 +8,8 @@
:chat-name "Имя чата" :chat-name "Имя чата"
:notifications-title "Уведомления и звуки" :notifications-title "Уведомления и звуки"
:offline "Оффлайн" :offline "Оффлайн"
:cancel "Отмена"
:next "Продолжить"
;drawer ;drawer
:invite-friends "Пригласить друзей" :invite-friends "Пригласить друзей"
@ -119,6 +121,7 @@
;contacts ;contacts
:contacts "Контакты" :contacts "Контакты"
:new-contact "Новый контакт" :new-contact "Новый контакт"
:edit-contacts "Редактирование контактов"
:show-all "ПОКАЗАТЬ ВСЕ" :show-all "ПОКАЗАТЬ ВСЕ"
:contacts-group-dapps "ÐApps" :contacts-group-dapps "ÐApps"
:contacts-group-people "Люди" :contacts-group-people "Люди"
@ -161,6 +164,9 @@
:group-chat-name "Имя чата" :group-chat-name "Имя чата"
:empty-group-chat-name "Введите имя" :empty-group-chat-name "Введите имя"
:illegal-group-chat-name "Выберите другое имя" :illegal-group-chat-name "Выберите другое имя"
:new-group "Новая группа"
:group-name "Название группы"
:reorder-groups "Упорядочить группы"
;participants ;participants
:add-participants "Добавить участников" :add-participants "Добавить участников"