diff --git a/.re-natal b/.re-natal index ad994fe623..8f9b7f3b7c 100644 --- a/.re-natal +++ b/.re-natal @@ -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", diff --git a/android/app/src/main/res/drawable-hdpi/icon_add_blue.png b/android/app/src/main/res/drawable-hdpi/icon_add_blue.png new file mode 100644 index 0000000000..bc625c7666 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_add_blue.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_back_dark.png b/android/app/src/main/res/drawable-hdpi/icon_back_dark.png new file mode 100644 index 0000000000..82558dfb9d Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_back_dark.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_check_on.png b/android/app/src/main/res/drawable-hdpi/icon_check_on.png new file mode 100644 index 0000000000..d8318e7fcb Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_check_on.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_hamburger_dark.png b/android/app/src/main/res/drawable-hdpi/icon_hamburger_dark.png new file mode 100644 index 0000000000..d15588f931 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_hamburger_dark.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_options_dark.png b/android/app/src/main/res/drawable-hdpi/icon_options_dark.png new file mode 100644 index 0000000000..daa1561d30 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_options_dark.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_options_gray.png b/android/app/src/main/res/drawable-hdpi/icon_options_gray.png new file mode 100644 index 0000000000..7fa48deb3c Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_options_gray.png differ diff --git a/android/app/src/main/res/drawable-hdpi/icon_search_dark.png b/android/app/src/main/res/drawable-hdpi/icon_search_dark.png new file mode 100644 index 0000000000..68be50ff40 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_search_dark.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_add_blue.png b/android/app/src/main/res/drawable-mdpi/icon_add_blue.png new file mode 100644 index 0000000000..789dcb4b99 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_add_blue.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_back_dark.png b/android/app/src/main/res/drawable-mdpi/icon_back_dark.png new file mode 100644 index 0000000000..cf94e17080 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_back_dark.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_check_on.png b/android/app/src/main/res/drawable-mdpi/icon_check_on.png new file mode 100644 index 0000000000..a44f891d2f Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_check_on.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_hamburger_dark.png b/android/app/src/main/res/drawable-mdpi/icon_hamburger_dark.png new file mode 100644 index 0000000000..c36b03f670 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_hamburger_dark.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_options_dark.png b/android/app/src/main/res/drawable-mdpi/icon_options_dark.png new file mode 100644 index 0000000000..7256d6e8eb Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_options_dark.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_options_gray.png b/android/app/src/main/res/drawable-mdpi/icon_options_gray.png new file mode 100644 index 0000000000..098d4942ff Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_options_gray.png differ diff --git a/android/app/src/main/res/drawable-mdpi/icon_search_dark.png b/android/app/src/main/res/drawable-mdpi/icon_search_dark.png new file mode 100644 index 0000000000..a17aa09495 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_search_dark.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_add_blue.png b/android/app/src/main/res/drawable-xhdpi/icon_add_blue.png new file mode 100644 index 0000000000..2fcfcb6e06 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_add_blue.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_back_dark.png b/android/app/src/main/res/drawable-xhdpi/icon_back_dark.png new file mode 100644 index 0000000000..f19f5d0804 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_back_dark.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_check_on.png b/android/app/src/main/res/drawable-xhdpi/icon_check_on.png new file mode 100644 index 0000000000..ef90004718 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_check_on.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_hamburger_dark.png b/android/app/src/main/res/drawable-xhdpi/icon_hamburger_dark.png new file mode 100644 index 0000000000..7ac556cc2d Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_hamburger_dark.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_options_dark.png b/android/app/src/main/res/drawable-xhdpi/icon_options_dark.png new file mode 100644 index 0000000000..af7dfd34ee Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_options_dark.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_options_gray.png b/android/app/src/main/res/drawable-xhdpi/icon_options_gray.png new file mode 100644 index 0000000000..af36912bda Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_options_gray.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/icon_search_dark.png b/android/app/src/main/res/drawable-xhdpi/icon_search_dark.png new file mode 100644 index 0000000000..0fbc32de6c Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_search_dark.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_add_blue.png b/android/app/src/main/res/drawable-xxhdpi/icon_add_blue.png new file mode 100644 index 0000000000..e339e0df06 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_add_blue.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_back_dark.png b/android/app/src/main/res/drawable-xxhdpi/icon_back_dark.png new file mode 100644 index 0000000000..ec8f01fed5 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_back_dark.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_check_on.png b/android/app/src/main/res/drawable-xxhdpi/icon_check_on.png new file mode 100644 index 0000000000..6388824eb6 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_check_on.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_hamburger_dark.png b/android/app/src/main/res/drawable-xxhdpi/icon_hamburger_dark.png new file mode 100644 index 0000000000..99f078b0bf Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_hamburger_dark.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_options_dark.png b/android/app/src/main/res/drawable-xxhdpi/icon_options_dark.png new file mode 100644 index 0000000000..670f5235fc Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_options_dark.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_options_gray.png b/android/app/src/main/res/drawable-xxhdpi/icon_options_gray.png new file mode 100644 index 0000000000..41b7456e34 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_options_gray.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_search_dark.png b/android/app/src/main/res/drawable-xxhdpi/icon_search_dark.png new file mode 100644 index 0000000000..85ba66b31b Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_search_dark.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_add_blue.png b/android/app/src/main/res/drawable-xxxhdpi/icon_add_blue.png new file mode 100644 index 0000000000..f80af67d19 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_add_blue.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_back_dark.png b/android/app/src/main/res/drawable-xxxhdpi/icon_back_dark.png new file mode 100644 index 0000000000..28760d89bf Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_back_dark.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_check_on.png b/android/app/src/main/res/drawable-xxxhdpi/icon_check_on.png new file mode 100644 index 0000000000..780956a86b Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_check_on.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_hamburger_dark.png b/android/app/src/main/res/drawable-xxxhdpi/icon_hamburger_dark.png new file mode 100644 index 0000000000..8f50831927 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_hamburger_dark.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_options_dark.png b/android/app/src/main/res/drawable-xxxhdpi/icon_options_dark.png new file mode 100644 index 0000000000..08ea8f9ffe Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_options_dark.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_options_gray.png b/android/app/src/main/res/drawable-xxxhdpi/icon_options_gray.png new file mode 100644 index 0000000000..f1535c3f3e Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_options_gray.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_search_dark.png b/android/app/src/main/res/drawable-xxxhdpi/icon_search_dark.png new file mode 100644 index 0000000000..8a24c2fb54 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_search_dark.png differ diff --git a/images/icon_dots_horizontal_dark.png b/images/icon_dots_horizontal_dark.png new file mode 100644 index 0000000000..e00f3865eb Binary files /dev/null and b/images/icon_dots_horizontal_dark.png differ diff --git a/ios/StatusIm/Images.xcassets/icon_add_blue.imageset/Contents.json b/ios/StatusIm/Images.xcassets/icon_add_blue.imageset/Contents.json new file mode 100644 index 0000000000..db6aecbb67 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/icon_add_blue.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/icon_add_blue.imageset/icon_add_blue.png b/ios/StatusIm/Images.xcassets/icon_add_blue.imageset/icon_add_blue.png new file mode 100644 index 0000000000..2fcfcb6e06 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/icon_add_blue.imageset/icon_add_blue.png differ diff --git a/ios/StatusIm/Images.xcassets/icon_back_dark.imageset/Contents.json b/ios/StatusIm/Images.xcassets/icon_back_dark.imageset/Contents.json new file mode 100644 index 0000000000..043e31174d --- /dev/null +++ b/ios/StatusIm/Images.xcassets/icon_back_dark.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/icon_back_dark.imageset/icon_back_dark.png b/ios/StatusIm/Images.xcassets/icon_back_dark.imageset/icon_back_dark.png new file mode 100644 index 0000000000..f19f5d0804 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/icon_back_dark.imageset/icon_back_dark.png differ diff --git a/ios/StatusIm/Images.xcassets/icon_check_on.imageset/Contents.json b/ios/StatusIm/Images.xcassets/icon_check_on.imageset/Contents.json new file mode 100644 index 0000000000..a5f8b13cf1 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/icon_check_on.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/icon_check_on.imageset/icon_check_on.png b/ios/StatusIm/Images.xcassets/icon_check_on.imageset/icon_check_on.png new file mode 100644 index 0000000000..ef90004718 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/icon_check_on.imageset/icon_check_on.png differ diff --git a/ios/StatusIm/Images.xcassets/icon_hamburger_dark.imageset/Contents.json b/ios/StatusIm/Images.xcassets/icon_hamburger_dark.imageset/Contents.json new file mode 100644 index 0000000000..14c6ed1c54 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/icon_hamburger_dark.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/icon_hamburger_dark.imageset/icon_hamburger_dark.png b/ios/StatusIm/Images.xcassets/icon_hamburger_dark.imageset/icon_hamburger_dark.png new file mode 100644 index 0000000000..7ac556cc2d Binary files /dev/null and b/ios/StatusIm/Images.xcassets/icon_hamburger_dark.imageset/icon_hamburger_dark.png differ diff --git a/ios/StatusIm/Images.xcassets/icon_options_dark.imageset/Contents.json b/ios/StatusIm/Images.xcassets/icon_options_dark.imageset/Contents.json new file mode 100644 index 0000000000..b74610d665 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/icon_options_dark.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/icon_options_dark.imageset/icon_dots_horizontal_dark.png b/ios/StatusIm/Images.xcassets/icon_options_dark.imageset/icon_dots_horizontal_dark.png new file mode 100644 index 0000000000..846998d755 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/icon_options_dark.imageset/icon_dots_horizontal_dark.png differ diff --git a/ios/StatusIm/Images.xcassets/icon_options_gray.imageset/Contents.json b/ios/StatusIm/Images.xcassets/icon_options_gray.imageset/Contents.json new file mode 100644 index 0000000000..c675431cd9 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/icon_options_gray.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/icon_options_gray.imageset/icon_dots_horizontal_gray.png b/ios/StatusIm/Images.xcassets/icon_options_gray.imageset/icon_dots_horizontal_gray.png new file mode 100644 index 0000000000..01b2320081 Binary files /dev/null and b/ios/StatusIm/Images.xcassets/icon_options_gray.imageset/icon_dots_horizontal_gray.png differ diff --git a/ios/StatusIm/Images.xcassets/icon_search_dark.imageset/Contents.json b/ios/StatusIm/Images.xcassets/icon_search_dark.imageset/Contents.json new file mode 100644 index 0000000000..255462e697 --- /dev/null +++ b/ios/StatusIm/Images.xcassets/icon_search_dark.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/ios/StatusIm/Images.xcassets/icon_search_dark.imageset/icon_search_dark.png b/ios/StatusIm/Images.xcassets/icon_search_dark.imageset/icon_search_dark.png new file mode 100644 index 0000000000..0fbc32de6c Binary files /dev/null and b/ios/StatusIm/Images.xcassets/icon_search_dark.imageset/icon_search_dark.png differ diff --git a/package.json b/package.json index de30bad5f3..b56cea2ea1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/status_im/android/core.cljs b/src/status_im/android/core.cljs index 9fc2105c4f..5bfd5d6fc0 100644 --- a/src/status_im/android/core.cljs +++ b/src/status_im/android/core.cljs @@ -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) diff --git a/src/status_im/android/platform.cljs b/src/status_im/android/platform.cljs index 7d565ad2d1..2875810d98 100644 --- a/src/status_im/android/platform.cljs +++ b/src/status_im/android/platform.cljs @@ -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")) diff --git a/src/status_im/chat/handlers/webview_bridge.cljs b/src/status_im/chat/handlers/webview_bridge.cljs index b9fcbb27f8..a26726a1b2 100644 --- a/src/status_im/chat/handlers/webview_bridge.cljs +++ b/src/status_im/chat/handlers/webview_bridge.cljs @@ -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)}) diff --git a/src/status_im/commands/handlers/loading.cljs b/src/status_im/commands/handlers/loading.cljs index f5c0da206c..be3341493d 100644 --- a/src/status_im/commands/handlers/loading.cljs +++ b/src/status_im/commands/handlers/loading.cljs @@ -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}]]))))) diff --git a/src/status_im/components/confirm_button.cljs b/src/status_im/components/confirm_button.cljs new file mode 100644 index 0000000000..799d82b36d --- /dev/null +++ b/src/status_im/components/confirm_button.cljs @@ -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]]]) \ No newline at end of file diff --git a/src/status_im/components/context_menu.cljs b/src/status_im/components/context_menu.cljs new file mode 100644 index 0000000000..293eedad60 --- /dev/null +++ b/src/status_im/components/context_menu.cljs @@ -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])]])) \ No newline at end of file diff --git a/src/status_im/components/react.cljs b/src/status_im/components/react.cljs index 2feb470712..c7ec58bc17 100644 --- a/src/status_im/components/react.cljs +++ b/src/status_im/components/react.cljs @@ -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}])) diff --git a/src/status_im/components/styles.cljs b/src/status_im/components/styles.cljs index b6df42eaf0..f0df443e8e 100644 --- a/src/status_im/components/styles.cljs +++ b/src/status_im/components/styles.cljs @@ -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}}}) diff --git a/src/status_im/components/toolbar/actions.cljs b/src/status_im/components/toolbar/actions.cljs index d38c3bcf39..ecf66d40e1 100644 --- a/src/status_im/components/toolbar/actions.cljs +++ b/src/status_im/components/toolbar/actions.cljs @@ -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}) diff --git a/src/status_im/components/toolbar/styles.cljs b/src/status_im/components/toolbar/styles.cljs index de800ccc25..eabc5fce60 100644 --- a/src/status_im/components/toolbar/styles.cljs +++ b/src/status_im/components/toolbar/styles.cljs @@ -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}) diff --git a/src/status_im/components/toolbar/view.cljs b/src/status_im/components/toolbar/view.cljs index bb6778bec2..9210c9c735 100644 --- a/src/status_im/components/toolbar/view.cljs +++ b/src/status_im/components/toolbar/view.cljs @@ -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)) diff --git a/src/status_im/contacts/handlers.cljs b/src/status_im/contacts/handlers.cljs index 5e26ad9ddf..5a6284e9e0 100644 --- a/src/status_im/contacts/handlers.cljs +++ b/src/status_im/contacts/handlers.cljs @@ -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] "")))) diff --git a/src/status_im/contacts/screen.cljs b/src/status_im/contacts/screen.cljs index 7b83b460e7..f56dc110af 100644 --- a/src/status_im/contacts/screen.cljs +++ b/src/status_im/contacts/screen.cljs @@ -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])]) diff --git a/src/status_im/contacts/styles.cljs b/src/status_im/contacts/styles.cljs index c19c8440ed..b3841b33b2 100644 --- a/src/status_im/contacts/styles.cljs +++ b/src/status_im/contacts/styles.cljs @@ -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 diff --git a/src/status_im/contacts/subs.cljs b/src/status_im/contacts/subs.cljs index cc884b8f97..6d34d6fa9e 100644 --- a/src/status_im/contacts/subs.cljs +++ b/src/status_im/contacts/subs.cljs @@ -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)] diff --git a/src/status_im/contacts/views/contact.cljs b/src/status_im/contacts/views/contact.cljs index 3096290342..67cc593ae9 100644 --- a/src/status_im/contacts/views/contact.cljs +++ b/src/status_im/contacts/views/contact.cljs @@ -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])]]]]) diff --git a/src/status_im/contacts/views/contact_inner.cljs b/src/status_im/contacts/views/contact_inner.cljs index 9e245d902b..df185d5310 100644 --- a/src/status_im/contacts/views/contact_inner.cljs +++ b/src/status_im/contacts/views/contact_inner.cljs @@ -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 diff --git a/src/status_im/contacts/views/contact_list.cljs b/src/status_im/contacts/views/contact_list.cljs index 4260f25daa..0025f6c045 100644 --- a/src/status_im/contacts/views/contact_list.cljs +++ b/src/status_im/contacts/views/contact_list.cljs @@ -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]]]) diff --git a/src/status_im/data_store/chats.cljs b/src/status_im/data_store/chats.cljs index 4ed378a66b..a0c0996181 100644 --- a/src/status_im/data_store/chats.cljs +++ b/src/status_im/data_store/chats.cljs @@ -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] diff --git a/src/status_im/data_store/contact_groups.cljs b/src/status_im/data_store/contact_groups.cljs new file mode 100644 index 0000000000..4854c5112a --- /dev/null +++ b/src/status_im/data_store/contact_groups.cljs @@ -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)) diff --git a/src/status_im/data_store/realm/contact_groups.cljs b/src/status_im/data_store/realm/contact_groups.cljs new file mode 100644 index 0000000000..122e0770c0 --- /dev/null +++ b/src/status_im/data_store/realm/contact_groups.cljs @@ -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))) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/account/core.cljs b/src/status_im/data_store/realm/schemas/account/core.cljs index 85a9629e22..121b62930b 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -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}]) diff --git a/src/status_im/data_store/realm/schemas/account/v2/core.cljs b/src/status_im/data_store/realm/schemas/account/v2/core.cljs index 3cdb8d876b..aaa93940b8 100644 --- a/src/status_im/data_store/realm/schemas/account/v2/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/v2/core.cljs @@ -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)) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/account/v5/contact_group.cljs b/src/status_im/data_store/realm/schemas/account/v5/contact_group.cljs new file mode 100644 index 0000000000..c1e4d6eee6 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v5/contact_group.cljs @@ -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")) diff --git a/src/status_im/data_store/realm/schemas/account/v5/core.cljs b/src/status_im/data_store/realm/schemas/account/v5/core.cljs new file mode 100644 index 0000000000..13d46af137 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v5/core.cljs @@ -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)) diff --git a/src/status_im/data_store/realm/schemas/account/v5/group_contact.cljs b/src/status_im/data_store/realm/schemas/account/v5/group_contact.cljs new file mode 100644 index 0000000000..c2df25137f --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v5/group_contact.cljs @@ -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")) \ No newline at end of file diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index b7adefea48..0d6a14f620 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -20,6 +20,7 @@ :new-contact-identity "" :contacts {} + :contact-groups {} :discoveries {} :discover-search-tags [] :tags {} diff --git a/src/status_im/handlers.cljs b/src/status_im/handlers.cljs index aae8015eac..0d4a01d122 100644 --- a/src/status_im/handlers.cljs +++ b/src/status_im/handlers.cljs @@ -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]) diff --git a/src/status_im/ios/core.cljs b/src/status_im/ios/core.cljs index 8734c9b925..6c3c20fb67 100644 --- a/src/status_im/ios/core.cljs +++ b/src/status_im/ios/core.cljs @@ -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 diff --git a/src/status_im/ios/platform.cljs b/src/status_im/ios/platform.cljs index f1d008c2b9..6a2d8d4627 100644 --- a/src/status_im/ios/platform.cljs +++ b/src/status_im/ios/platform.cljs @@ -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 diff --git a/src/status_im/new_group/handlers.cljs b/src/status_im/new_group/handlers.cljs index 676707b319..836d6858bf 100644 --- a/src/status_im/new_group/handlers.cljs +++ b/src/status_im/new_group/handlers.cljs @@ -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)) diff --git a/src/status_im/new_group/screen_private.cljs b/src/status_im/new_group/screen_private.cljs index cc21d46a26..debb4d4a20 100644 --- a/src/status_im/new_group/screen_private.cljs +++ b/src/status_im/new_group/screen_private.cljs @@ -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]))])])) diff --git a/src/status_im/new_group/styles.cljs b/src/status_im/new_group/styles.cljs index cf4b583ab5..25615149f0 100644 --- a/src/status_im/new_group/styles.cljs +++ b/src/status_im/new_group/styles.cljs @@ -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}) \ No newline at end of file diff --git a/src/status_im/new_group/subs.cljs b/src/status_im/new_group/subs.cljs index 6753a5eacb..738b55ac04 100644 --- a/src/status_im/new_group/subs.cljs +++ b/src/status_im/new_group/subs.cljs @@ -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)))))) diff --git a/src/status_im/new_group/views/contact.cljs b/src/status_im/new_group/views/contact.cljs index be080bda05..20fca109ff 100644 --- a/src/status_im/new_group/views/contact.cljs +++ b/src/status_im/new_group/views/contact.cljs @@ -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])]]]]) diff --git a/src/status_im/new_group/views/contact_list.cljs b/src/status_im/new_group/views/contact_list.cljs new file mode 100644 index 0000000000..69d08511bb --- /dev/null +++ b/src/status_im/new_group/views/contact_list.cljs @@ -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])])]) diff --git a/src/status_im/participants/views/contact.cljs b/src/status_im/participants/views/contact.cljs index d474fb3305..1b3829b835 100644 --- a/src/status_im/participants/views/contact.cljs +++ b/src/status_im/participants/views/contact.cljs @@ -19,4 +19,4 @@ [item-checkbox {:onToggle (on-toggle whisper-identity) :checked checked :size 30}] - [contact-inner-view contact]]) + [contact-inner-view {:contact contact}]]) diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index 12916d3c2f..290a41f1ca 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -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" diff --git a/src/status_im/translations/ru.cljs b/src/status_im/translations/ru.cljs index a2239510e3..501b1d68a3 100644 --- a/src/status_im/translations/ru.cljs +++ b/src/status_im/translations/ru.cljs @@ -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 "Добавить участников"