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",
"react-native-fs",
"react-native-dialogs",
"react-native-popup-menu",
"react-native-image-resizer",
"react-native-image-crop-picker",
"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-network-info": "github:alwx/react-native-network-info",
"react-native-orientation": "github:youennPennarun/react-native-orientation",
"react-native-popup-menu": "^0.7.1",
"react-native-qrcode": "^0.2.2",
"react-native-randombytes": "^2.1.0",
"react-native-share": "1.0.17",

View File

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

View File

@ -1,6 +1,5 @@
(ns status-im.android.platform
(:require [status-im.components.styles :as styles]
[status-im.components.toolbar.styles :refer [toolbar-background2]]))
(:require [status-im.components.styles :as styles]))
(def component-styles
{:status-bar {:default {:height 0
@ -8,7 +7,7 @@
:color styles/color-white}
:main {:height 0
:bar-style "dark-content"
:color toolbar-background2}
:color styles/color-white}
:transparent {:height 20
:bar-style "light-content"
:translucent? true
@ -34,8 +33,17 @@
:item {:status-text {:color styles/color-black
:line-height 22
:font-size 14}}}
:contacts {:subtitle {:color styles/color-gray2
:font-size 14}}
:contacts {:subtitle {:color styles/color-gray4
: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}
:input-label {:left 4}
:input-error-text {:margin-left 4}
@ -47,7 +55,8 @@
:toolbar-last-activity {:color styles/text2-color
:background-color :transparent
:top 0
:font-size 12}})
:font-size 12}
:toolbar-title-container {:padding-left 16}})
(def fonts
{:light {:font-family "sans-serif-light"}
@ -56,7 +65,6 @@
:toolbar-title {:font-family "sans-serif"}})
;; Dialogs
(def react-native-dialogs (js/require "react-native-dialogs"))

View File

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

View File

@ -9,8 +9,10 @@
[status-im.commands.utils :refer [reg-handler]]
[status-im.constants :refer [console-chat-id wallet-chat-id]]
[taoensso.timbre :as log]
[status-im.i18n :refer [label]]
[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")
@ -164,3 +166,28 @@
(reg-handler ::clear-commands-callbacks
(fn [db [chat-id]]
(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))))]))
(defn icon
([n] (icon n {}))
([n] (icon n st/icon-default))
([n style]
[image {:source {:uri (keyword (str "icon_" (name n)))}
:style style}]))

View File

@ -8,11 +8,16 @@
(def color-gray "#838c93de")
(def color-gray2 "#8f838c93")
(def color-gray3 "#00000040")
(def color-gray4 "#939ba1")
(def color-gray5 "#d9dae1")
(def color-steel "#838b91")
(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-red "red")
(def color-light-red "#e86363")
(def color-separator "#D6D6D6")
(def text1-color color-black)
@ -23,15 +28,16 @@
(def new-messages-count-color color-blue-transparent)
(def chat-background color-light-gray)
(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 flex
{:flex 1})
(def icon-search
{:width 17
:height 17})
{:width 24
:height 24})
(def create-icon
{:fontSize 20
@ -39,8 +45,12 @@
:color :white})
(def icon-back
{:width 8
:height 14})
{:width 24
:height 24})
(def icon-default
{:width 24
:height 24})
(def icon-add
{:width 14
@ -78,3 +88,25 @@
(def button-input
{:flex 1
: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
{:image {:source nil
:style st/action-search}})
:style st/action-default}})
(defn hamburger [handler]
{:image {:source {:uri :icon_hamburger}
:style st/action-hamburger}
{:image {:source {:uri :icon_hamburger_dark}
:style st/action-default}
:handler handler})
(defn add [handler]
{: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})
(defn search [handler]
{:image {:source {:uri :icon_search}
:style st/action-search}
{:image {:source {:uri :icon_search_dark}
:style st/action-default}
:handler handler})
(defn back [handler]
{:image {:source {:uri :icon_back}
:style st/action-back}
{:image {:source {:uri :icon_back_dark}
:style st/action-default}
:handler handler})
(defn back-white [handler]
{:image {:source {:uri :icon_back_white}
:style st/action-back}
:style st/action-default}
:handler handler})

View File

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

View File

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

View File

@ -17,12 +17,24 @@
[status-im.utils.js-resources :as js-res]))
(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]]
(dissoc
(if group
(assoc db :contacts-group group)
db)
:contacts-filter))
(if group
(-> db
(assoc :contact-group group
:selected-contacts (into #{} (map :identity (:contacts group)))
:new-chat-name (:name group))
(update :toolbar-search assoc
:show :contact-list
:text ""))
db))
(defmethod nav/preload-data! :new-group
[db _]
@ -34,8 +46,7 @@
[db [_ _ click-handler]]
(-> db
(assoc-in [:toolbar-search :show] nil)
(assoc :contacts-click-handler click-handler
:contacts-filter nil)))
(assoc :contacts-click-handler click-handler)))
(register-handler :remove-contacts-click-handler
(fn [db]
@ -167,16 +178,15 @@
(defn save-contacts! [{:keys [new-contacts]} _]
(contacts/save-all new-contacts))
(defn update-pending-status [old-contacts {:keys [whisper-identity pending] :as contact}]
(let [{old-pending :pending
:as old-contact} (get old-contacts whisper-identity)]
(if old-contact
(assoc contact :pending (and old-pending pending))
(assoc contact :pending pending))))
(defn update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
(let [{old-pending :pending?
:as old-contact} (get old-contacts whisper-identity)
pending?' (if old-contact (and old-pending pending?) pending?)]
(assoc contact :pending? (boolean pending?'))))
(defn add-new-contacts
[{:keys [contacts] :as db} [_ new-contacts]]
(let [identities (set (map :whisper-identity contacts))
(let [identities (set (keys contacts))
new-contacts' (->> new-contacts
(map #(update-pending-status contacts %))
(remove #(identities (:whisper-identity %)))
@ -243,9 +253,9 @@
(register-handler :add-pending-contact
(u/side-effect!
(fn [{:keys [chats contacts]} [_ chat-id]]
(let [contact (if-let [contact-info (get-in chats [chat-id :contact-info])]
(read-string contact-info)
(assoc (get contacts chat-id) :pending false))
(let [contact (if-let [contact-info (get-in chats [chat-id :contact-info])]
(read-string contact-info)
(assoc (get contacts chat-id) :pending? false))
contact' (assoc contact :address (public-key->address chat-id))]
(dispatch [::prepare-contact contact'])
(dispatch [:watch-contact contact'])
@ -283,6 +293,8 @@
(let [{{:keys [public private]} :keypair
timestamp :timestamp} payload
prev-last-updated (get-in db [:contacts from :keys-last-updated])]
(when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from
:public-key public
@ -305,9 +317,19 @@
(after stop-watching-contact)
(u/side-effect!
(fn [_ [_ {:keys [whisper-identity] :as contact}]]
(dispatch [:update-contact! (assoc contact :pending true)])
(dispatch [:update-contact! (assoc contact :pending? true)])
(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
(fn [db [_ whisper-identity pred]]
(if-let [contact (contacts/get-by-id whisper-identity)]
@ -329,3 +351,14 @@
0 (dispatch [:hide-contact contact])
:default))
: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
text
image
icon
touchable-highlight
linear-gradient
scroll-view
@ -14,10 +15,12 @@
[status-im.components.action-button :refer [action-button
action-button-item]]
[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.styles :as tst]
[status-im.components.drawer.view :refer [open-drawer]]
[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.utils.platform :refer [platform-specific]]
[status-im.i18n :refer [label]]
@ -25,30 +28,45 @@
[status-im.components.styles :refer [color-blue
create-icon]]))
(def contacts-limit 50)
(def contacts-limit 5)
(defn toolbar-view [show-search?]
(let [new-contact? (get-in platform-specific [:contacts :new-contact-in-toolbar?])
actions (if new-contact?
[(act/add #(dispatch [:navigate-to :new-contact]))])]
(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])))})))
(def toolbar-options
[{:text (label :t/new-contact) :value #(dispatch [:navigate-to :new-contact])}
{:text (label :t/edit) :value #(dispatch [:set-in [:contacts-ui-props :edit?] true])}
{:text (label :t/new-group) :value #(dispatch [:open-contact-group-list])}])
(defn subtitle-view [subtitle contacts-count]
[view st/contact-group-header-inner
(defn toolbar-actions []
(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
(get-in platform-specific [:component-styles :contacts :subtitle]))
:uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?])
@ -58,7 +76,9 @@
(get-in platform-specific [:component-styles :contacts :subtitle]))
:uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?])
:font :medium}
(str contacts-count)]])
(str contacts-count)]
(when extended?
[options-btn group])])
(defn group-top-view []
[linear-gradient {:style st/contact-group-header-gradient-bottom
@ -68,37 +88,47 @@
[linear-gradient {:style st/contact-group-header-gradient-top
:colors st/contact-group-header-gradient-top-colors}])
(defn line-view []
[view {:style {:background-color "#D7D7D7"
:height 1}}])
(defn on-scroll-animation [e show-toolbar-shadow?]
(let [offset (.. e -nativeEvent -contentOffset -y)]
(reset! show-toolbar-shadow? (pos? offset))))
(defn contact-group-view [contacts contacts-count subtitle group click-handler]
(let [shadows? (get-in platform-specific [:contacts :group-block-shadows?])]
(defn contact-group-form [{:keys [contacts contacts-count group edit? click-handler]}]
(let [shadows? (get-in platform-specific [:contacts :group-block-shadows?])
subtitle (:name group)]
[view st/contact-group
[view st/contact-group-header
[subtitle-view subtitle contacts-count]]
(if shadows?
[group-top-view]
[line-view])
(when subtitle
[subtitle-view subtitle contacts-count group edit?])
(when (and subtitle shadows?)
[group-top-view])
[view
(doall
(map (fn [contact]
^{:key contact}
[contact-view {:contact contact
:extended? true
:on-click click-handler
:more-on-click nil}])
[contact-view
{:contact contact
:extended? edit?
: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))]
(when (<= contacts-limit (count contacts))
(when (< contacts-limit contacts-count)
[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
[text {:style st/show-all-text
:font :medium}
(label :t/show-all)]]]])
(if shadows?
[group-bottom-view]
[line-view])]))
(str (- contacts-count contacts-limit) " " (label :t/more))]]]])
(when shadows?
[group-bottom-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 []
[action-button {:button-color color-blue
@ -113,44 +143,35 @@
[ion-icon {:name :md-create
:style create-icon}]]])
(defn contact-list [_]
(let [peoples (subscribe [:get-added-people-with-limit contacts-limit])
dapps (subscribe [:get-added-dapps-with-limit contacts-limit])
people-count (subscribe [:added-people-count])
dapps-count (subscribe [:added-dapps-count])
click-handler (subscribe [:get :contacts-click-handler])
show-search (subscribe [:get-in [:toolbar-search :show]])
show-toolbar-shadow? (r/atom false)]
(fn [current-view?]
[view st/contacts-list-container
[toolbar-view (and current-view?
(= @show-search :contact-list))]
[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? (+ @dapps-count @people-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))))}
(when (pos? @dapps-count)
[contact-group-view
@dapps
@dapps-count
(label :t/contacts-group-dapps)
:dapps
@click-handler])
(when (pos? @people-count)
[contact-group-view
@peoples
@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])])))
(defview contact-list [current-view?]
[contacts [:get-added-contacts-with-limit contacts-limit]
contacts-count [:added-contacts-count]
click-handler [:get :contacts-click-handler]
edit? [:get-in [:contacts-ui-props :edit?]]
groups [:all-added-groups]
show-toolbar-shadow? (r/atom false)]
[view st/contacts-list-container
(if edit?
[toolbar-edit]
[toolbar-view])
(when @show-toolbar-shadow?
[linear-gradient {:style st/contact-group-header-gradient-bottom
:colors st/contact-group-header-gradient-bottom-colors}])
(if (pos? (+ (count groups) contacts-count))
[scroll-view {:style st/contact-groups
:onScroll #(on-scroll-animation % show-toolbar-shadow?)}
(when (pos? contacts-count)
[contact-group-form {:contacts contacts
:contacts-count contacts-count
:edit? edit?
:click-handler click-handler}])
(for [group groups]
^{:key group}
[contact-group-view {:group group
:edit? edit?
:click-handler 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 (and (not edit?) (get-in platform-specific [:contacts :action-button?]))
[contacts-action-button])])

View File

@ -6,7 +6,7 @@
color-separator
color-gray2
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]))
;; Contacts list
@ -15,6 +15,9 @@
{:height 2
:background-color toolbar-background2})
(def toolbar-actions
{:flex-direction :row})
(def contact-groups
{:flex 1
:background-color toolbar-background2})
@ -43,22 +46,13 @@
(def contact-group
{: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
{:flex 1
:margin-left 16})
{:margin-left 16})
(def contact-group-count
{:margin-right 14})
{:flex 1
:margin-left 8
:opacity 0.6})
(def contact-group-header-gradient-top
{:flexDirection :row
@ -79,9 +73,6 @@
["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
@ -95,6 +86,9 @@
;; ios only:
:letterSpacing 0.5})
(def contact-separator-container
{:background-color color-white})
(def contact-container
{:flex-direction :row
:background-color color-white})
@ -129,8 +123,8 @@
(def option-inner-image
{:width 24
:height 18
:top 16
:left 13})
:top 16
:left 13})
(def group-icon
(assoc option-inner-image
@ -167,14 +161,18 @@
:color text2-color})
(def more-btn
{:width 56
{:width 24
:height 56
:margin-right 14
:alignItems :center
:justifyContent :center})
(def more-btn-icon
{:width 4
:height 16})
(def search-btn
{:width 24
:height 56
:margin-right 24
:alignItems :center
:justifyContent :center})
; New contact

View File

@ -1,7 +1,8 @@
(ns status-im.contacts.subs
(:require-macros [reagent.ratom :refer [reaction]])
(: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
(fn [db [_ k]]
@ -29,47 +30,66 @@
(sort-contacts)
(reaction)))))
(register-sub :all-added-people
(fn []
(let [contacts (subscribe [:all-added-contacts])]
(reaction (remove :dapp? @contacts)))))
(register-sub :all-added-group-contacts
(fn [db [_ group-id]]
(let [contacts (subscribe [:all-added-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
(fn []
(let [contacts (subscribe [:all-added-contacts])]
(reaction (filter :dapp? @contacts)))))
(register-sub :get-added-people-with-limit
(fn [_ [_ limit]]
(let [contacts (subscribe [:all-added-people])]
(register-sub :all-added-group-contacts-with-limit
(fn [db [_ group-id limit]]
(let [contacts (subscribe [:all-added-group-contacts group-id])]
(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]]
(let [contacts (subscribe [:all-added-dapps])]
(let [contacts (subscribe [:all-added-contacts])]
(reaction (take limit @contacts)))))
(register-sub :added-people-count
(register-sub :added-contacts-count
(fn [_ _]
(let [contacts (subscribe [:all-added-people])]
(let [contacts (subscribe [:all-added-contacts])]
(reaction (count @contacts)))))
(register-sub :added-dapps-count
(fn [_ _]
(let [contacts (subscribe [:all-added-dapps])]
(reaction (count @contacts)))))
(register-sub :all-added-groups
(fn [db _]
(let [groups (reaction (vals (:contact-groups @db)))]
(->> (remove :pending? @groups)
(sort-by :order >)
(reaction)))))
(defn get-contact-letter [contact]
(when-let [letter (first (:name contact))]
(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
(fn [db _]
(let [contacts (reaction (:contacts @db))
pred (subscribe [:get :contacts-filter])]
(let [contacts (reaction (:contacts @db))]
(reaction
(let [ordered (sort-contacts @contacts)
ordered (if @pred (filter @pred ordered) ordered)]
(let [ordered (sort-contacts @contacts)]
(reduce (fn [prev cur]
(let [prev-letter (get-contact-letter (last prev))
cur-letter (get-contact-letter cur)]

View File

@ -4,31 +4,40 @@
[re-frame.core :refer [dispatch]]
[status-im.contacts.styles :as st]
[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]]))
(defn- on-press [{:keys [whisper-identity] :as contact}]
(dispatch [:send-contact-request! contact])
(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]
[view st/letter-container
(when 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
:keys [extended? letter? on-click more-on-click info]}]
:keys [extended? letter? on-click extend-options info]}]
[chat [:get-chat whisper-identity]]
[touchable-highlight
{:on-press #((or on-click on-press) contact)}
[view st/contact-container
(when letter?
[letter-view letter])
[contact-inner-view contact info]
(when (and extended? (not dapp?))
[touchable-highlight
{:on-press #((or more-on-click more-on-press) contact)}
[view st/more-btn
[icon :more_vertical st/more-btn-icon]]])]])
(when-not extended?
{:on-press #((or on-click on-press) contact)})
[view
[view st/contact-container
(when letter?
[letter-view letter])
[contact-inner-view {:contact contact :info info}]
(when extended?
[options-btn contact extend-options])]
[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]])
(defn contact-inner-view
([contact]
(contact-inner-view contact nil))
([{:keys [whisper-identity name] :as contact} info]
[view st/contact-inner-container
([{:keys [info style] {:keys [whisper-identity name] :as contact} :contact}]
[view (merge st/contact-inner-container style)
[contact-photo contact]
[view st/info-container
[text {:style st/name-text

View File

@ -9,7 +9,7 @@
[status-im.contacts.views.contact :refer [contact-view]]
[status-im.components.text-field.view :refer [text-field]]
[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.styles :refer [toolbar-background1]]
[status-im.components.drawer.view :refer [drawer-view open-drawer]]
@ -17,8 +17,7 @@
[status-im.contacts.styles :as st]
[status-im.utils.listview :as lw]
[status-im.i18n :refer [label]]
[status-im.utils.platform :refer [platform-specific]]
[status-im.contacts.views.contact-inner :refer [contact-inner-view]]))
[status-im.utils.platform :refer [platform-specific]]))
(defn new-group-chat-view []
[view
@ -46,6 +45,7 @@
(defn render-row [chat-modal click-handler action params]
(fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:letter? chat-modal
:on-click (when click-handler
@ -64,58 +64,62 @@
label]]]]])
(defview contact-list-toolbar []
[group [:get :contacts-group]
modal [:get :modal]]
[group [:get :contacts-group]
modal [:get :modal]
show-search [:get-in [:toolbar-search :show]]]
[view
[status-bar]
[toolbar {:title (label (if-not group
:t/contacts
(if (= group :dapps)
:t/contacts-group-dapps
:t/contacts-group-new-chat)))
:nav-action (when modal
(act/back #(dispatch [:navigate-back])))
:background-color toolbar-background1
:style (get-in platform-specific [:component-styles :toolbar])
:actions [(act/search #())]}]])
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-key :contact-list
:title (if-not group
(label :t/contacts)
(or (:name group) (label :t/contacts-group-new-chat)))
:search-placeholder (label :t/search-for)
:actions (when modal
(act/back #(dispatch [:navigate-back])))})])
(defview contact-list []
[contacts [:contacts-with-letters]
group [:get :contacts-group]
modal [:get :modal]
click-handler [:get :contacts-click-handler]
action [:get :contacts-click-action]
(defview contacts-list-view [group modal click-handler action]
[contacts [:all-added-group-contacts-filtered (:group-id group)]
params [:get :contacts-click-params]]
(let [show-new-group-chat? (and (= group :people)
(get-in platform-specific [:chats :new-chat-in-toolbar?]))]
[drawer-view
[view st/contacts-list-container
[contact-list-toolbar]
;; todo add stub
(when modal
[view
[contact-list-entry {:click-handler #(do
(dispatch [:send-to-webview-bridge
{:event (name :webview-send-transaction)}])
(dispatch [:navigate-back]))
:icon :icon_enter_address
: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))}]])
(when contacts
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (render-row modal click-handler action params)
:bounces false
:renderHeader #(list-item
[view
(if show-new-group-chat?
[new-group-chat-view])
[view st/spacing-top]])
:renderFooter #(list-item [view st/spacing-bottom])
:style st/contacts-list}])]]))
(when contacts
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (render-row modal click-handler action params)
:bounces false
:keyboardShouldPersistTaps true
:renderHeader #(list-item
[view
(if show-new-group-chat?
[new-group-chat-view])
[view st/spacing-top]])
:renderFooter #(list-item [view st/spacing-bottom])
:style st/contacts-list}])))
(defview contact-list []
[action [:get :contacts-click-action]
modal [:get :modal]
click-handler [:get :contacts-click-handler]
group [:get :contacts-group]]
[drawer-view
[view st/contacts-list-container
[contact-list-toolbar]
;; todo add stub
(when modal
[view
[contact-list-entry {:click-handler #(do
(dispatch [:send-to-webview-bridge
{:event (name :webview-send-transaction)}])
(dispatch [:navigate-back]))
:icon :icon_enter_address
: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?]))
(defn- normalize-contacts
[chats]
(map #(update % :contacts vals) chats))
[item]
(update item :contacts vals))
(defn get-all
[]
(normalize-contacts (data-store/get-all-active)))
(map normalize-contacts (data-store/get-all-active)))
(defn get-by-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.v3.core :as v3]
[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
(def schemas [{:schema v1/schema
@ -17,4 +18,7 @@
:migration v3/migration}
{:schema v4/schema
: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]
(log/debug "migrating v2 account database: " 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 ""
:contacts {}
:contact-groups {}
:discoveries {}
:discover-search-tags []
:tags {}

View File

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

View File

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

View File

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

View File

@ -4,7 +4,9 @@
[status-im.utils.handlers :refer [register-handler]]
[status-im.components.styles :refer [default-chat-color]]
[status-im.data-store.chats :as chats]
[status-im.data-store.contact-groups :as groups]
[clojure.string :as s]
[status-im.i18n :refer [label]]
[status-im.utils.handlers :as u]
[status-im.utils.random :as random]
[taoensso.timbre :refer-macros [debug]]
@ -86,7 +88,7 @@
:private private-key}
:callback #(dispatch [:incoming-message %1 %2])})))
(register-handler :create-new-group
(register-handler :create-new-group-chat
(-> prepare-chat
((enrich add-chat))
((after create-chat!))
@ -161,6 +163,98 @@
:keypair keypair
: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
[db]
(dissoc db :public-group/topic))

View File

@ -2,6 +2,7 @@
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch]]
[status-im.resources :as res]
[status-im.contacts.views.contact :refer [contact-view]]
[status-im.components.react :refer [view
text
image
@ -10,10 +11,12 @@
list-view
list-item]]
[status-im.components.text-field.view :refer [text-field]]
[status-im.components.confirm-button :refer [confirm-button]]
[status-im.components.styles :refer [color-blue
separator-color]]
[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.new-group.views.contact :refer [new-group-contact]]
[status-im.new-group.styles :as st]
@ -21,7 +24,7 @@
[status-im.i18n :refer [label]]
[cljs.spec :as s]))
(defview new-group-toolbar []
(defview new-chat-group-toolbar []
[new-chat-name [:get :new-chat-name]]
(let [create-btn-enabled? (s/valid? ::v/name new-chat-name)]
[view
@ -31,15 +34,13 @@
:actions [{:image {:source res/v ;; {:uri "icon_search"}
:style (st/toolbar-icon 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 []
[new-chat-name [:get :new-chat-name]]
[view
[text-field
{:error (cond
(not (s/valid? ::v/not-empty-string new-chat-name))
(label :t/empty-group-chat-name)
{:error (when
(not (s/valid? ::v/not-illegal-name new-chat-name))
(label :t/illegal-group-chat-name))
:wrapper-style st/group-chat-name-wrapper
@ -54,7 +55,7 @@
(defview new-group []
[contacts [:all-added-contacts]]
[view st/new-group-container
[new-group-toolbar]
[new-chat-group-toolbar]
[view st/chat-name-container
[text {:style st/members-text
:font :medium}
@ -64,11 +65,64 @@
:font :medium}
(label :t/members-title)]
#_[touchable-highlight {:on-press (fn [])}
[view st/add-container
[icon :add_gray st/add-icon]
[text {:style st/add-text} (label :t/add-members)]]]
[view st/add-container
[icon :add_gray st/add-icon]
[text {:style st/add-text} (label :t/add-members)]]]
[list-view
{:dataSource (to-datasource contacts)
:renderRow (fn [row _ _]
(list-item [new-group-contact row]))
: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
color-blue
text1-color
text2-color]]
text2-color
color-light-blue
color-light-red
selected-contact-color
color-gray4]]
[status-im.utils.platform :refer [platform-specific]]))
(defn toolbar-icon [enabled?]
@ -37,27 +41,49 @@
(def group-chat-name-wrapper
{: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
{:margin-top 24
:margin-bottom 8
:color text2-color
:font-size 14
:line-height 20})
{:margin-top 10
:margin-bottom 8
:letter-spacing -0.2
:color color-gray4
:font-size 16
:line-height 19})
(def add-container
{:flex-direction :row
:margin-bottom 16})
:align-items :center
:margin-top 16
:margin-bottom 16
:margin-right 20})
(def add-icon
{:margin-vertical 18
:margin-horizontal 3
:width 17
:height 17})
{:align-items :center
:width 24
:height 24})
(def add-text
{:margin-top 16
:margin-left 32
:color text2-color
{:margin-left 32
:color color-light-blue
: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
:line-height 20})
@ -70,9 +96,25 @@
:align-items :center
:height 56})
(def contact-item-checkbox
{:outer-size 20
:filter-size 16
:inner-size 12
:outer-color color-blue
:inner-color color-blue})
(def selected-contact
{:background-color selected-contact-color})
(def icon-check-container
(merge (get-in platform-specific [:component-styles :contacts :icon-check])
{: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
(: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]))
(register-sub :is-contact-selected?
(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
(:require-macros [status-im.utils.views :refer [defview]])
(: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.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]
(fn [checked?]
(let [action (if checked? :select-contact :deselect-contact)]
(dispatch [action whisper-identity]))))
(defn on-toggle [checked? whisper-identity]
(let [action (if checked? :deselect-contact :select-contact)]
(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}]
[checked [:is-contact-selected? whisper-identity]]
[view st/contact-container
[item-checkbox (merge {:on-toggle (on-toggle whisper-identity)
:checked checked}
st/contact-item-checkbox)]
[contact-inner-view contact]])
[touchable-highlight {:on-press #(on-toggle checked whisper-identity)}
[view
[view (merge st/contact-container (when checked {:style st/selected-contact}))
[contact-inner-view (merge {:contact 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)
:checked checked
:size 30}]
[contact-inner-view contact]])
[contact-inner-view {:contact contact}]])

View File

@ -10,6 +10,7 @@
:offline "Offline"
:search-for "Search for..."
:cancel "Cancel"
:next "Next"
;drawer
:invite-friends "Invite friends"
@ -141,8 +142,10 @@
;contacts
:contacts "Contacts"
:new-contact "New Contact"
:remove-contact "Remove contact"
:new-contact "New contact"
:delete-contact "Delete contact"
:remove-from-group "Remove from group"
:edit-contacts "Edit contacts"
:show-all "SHOW ALL"
:contacts-group-dapps "ÐApps"
:contacts-group-people "People"
@ -150,6 +153,7 @@
:no-contacts "No contacts yet"
:show-qr "Show QR"
:enter-address "Enter address"
:more "more"
;group-settings
:remove "Remove"
@ -185,6 +189,13 @@
:group-chat-name "Chat name"
:empty-group-chat-name "Please enter a 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
:add-participants "Add Participants"

View File

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