diff --git a/components/src/status_im/ui/components/react.cljs b/components/src/status_im/ui/components/react.cljs index 5d3327cb27..00451fcc2e 100644 --- a/components/src/status_im/ui/components/react.cljs +++ b/components/src/status_im/ui/components/react.cljs @@ -80,6 +80,9 @@ (def animated-view-class (reagent/adapt-react-class (.-View animated))) +(def animated-flat-list-class + (reagent/adapt-react-class (.-FlatList animated))) + (defn animated-view [props & content] (vec (conj content props animated-view-class))) diff --git a/externs.js b/externs.js index 37eea15934..71cfecfd70 100644 --- a/externs.js +++ b/externs.js @@ -522,6 +522,7 @@ var TopLevel = { "version" : function () {}, "vibrate" : function () {}, "View" : function () {}, + "FlatList" : function () {}, "warn" : function () {}, "WebView" : function () {}, "WebViewBridgeModule" : function () {}, diff --git a/fiddle/src/status_im/react_native/js_dependencies.cljs b/fiddle/src/status_im/react_native/js_dependencies.cljs index 1a500fb2a9..6d6b70cdd3 100644 --- a/fiddle/src/status_im/react_native/js_dependencies.cljs +++ b/fiddle/src/status_im/react_native/js_dependencies.cljs @@ -14,8 +14,9 @@ (def qr-code (fn [] #js {})) (def react-native #js {:NativeModules #js {} - :Animated #js {:View #js {} - :Text #js {}} + :Animated #js {:View #js {} + :FlatList #js {} + :Text #js {}} :DeviceEventEmitter #js {:addListener (fn [])} :Dimensions #js {:get (fn [])}}) (def vector-icons (fn [] #js {:default #js {}})) diff --git a/ios/StatusIm/Images.xcassets/remove_contact.imageset/Contents.json b/ios/StatusIm/Images.xcassets/remove-contact.imageset/Contents.json similarity index 100% rename from ios/StatusIm/Images.xcassets/remove_contact.imageset/Contents.json rename to ios/StatusIm/Images.xcassets/remove-contact.imageset/Contents.json diff --git a/ios/StatusIm/Images.xcassets/remove_contact.imageset/remove_contact.png b/ios/StatusIm/Images.xcassets/remove-contact.imageset/remove_contact.png similarity index 100% rename from ios/StatusIm/Images.xcassets/remove_contact.imageset/remove_contact.png rename to ios/StatusIm/Images.xcassets/remove-contact.imageset/remove_contact.png diff --git a/ios/StatusIm/Images.xcassets/remove_contact.imageset/remove_contact@2x.png b/ios/StatusIm/Images.xcassets/remove-contact.imageset/remove_contact@2x.png similarity index 100% rename from ios/StatusIm/Images.xcassets/remove_contact.imageset/remove_contact@2x.png rename to ios/StatusIm/Images.xcassets/remove-contact.imageset/remove_contact@2x.png diff --git a/ios/StatusIm/Images.xcassets/remove_contact.imageset/remove_contact@3x.png b/ios/StatusIm/Images.xcassets/remove-contact.imageset/remove_contact@3x.png similarity index 100% rename from ios/StatusIm/Images.xcassets/remove_contact.imageset/remove_contact@3x.png rename to ios/StatusIm/Images.xcassets/remove-contact.imageset/remove_contact@3x.png diff --git a/src/status_im/contact/block.cljs b/src/status_im/contact/block.cljs index a75ccf6062..a4625892a7 100644 --- a/src/status_im/contact/block.cljs +++ b/src/status_im/contact/block.cljs @@ -86,12 +86,4 @@ {:db (-> db (update :contacts/blocked disj public-key) (assoc-in [:contacts/contacts public-key] contact))} - (contacts-store/save-contact contact)))) - -(fx/defn block-contact-confirmation - [cofx public-key] - {:utils/show-confirmation - {:title (i18n/label :t/block-contact) - :content (i18n/label :t/block-contact-details) - :confirm-button-text (i18n/label :t/to-block) - :on-accept #(re-frame/dispatch [:contact.ui/block-contact-confirmed public-key])}}) + (contacts-store/save-contact contact)))) \ No newline at end of file diff --git a/src/status_im/contact/core.cljs b/src/status_im/contact/core.cljs index 8f9f23e29b..edb56c1d73 100644 --- a/src/status_im/contact/core.cljs +++ b/src/status_im/contact/core.cljs @@ -81,6 +81,17 @@ (send-contact-request contact) (mailserver/process-next-messages-request))))) +(fx/defn remove-contact + "Remove a contact from current account's contact list" + {:events [:contact.ui/remove-contact-pressed]} + [{:keys [db] :as cofx} {:keys [public-key] :as contact}] + (let [new-contact (update contact + :system-tags + (fnil #(disj % :contact/added) #{}))] + (fx/merge cofx + {:db (assoc-in db [:contacts/contacts public-key] new-contact)} + (contacts-store/save-contact new-contact)))) + (fx/defn create-contact "Create entry in contacts" [{:keys [db] :as cofx} public-key] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index f8c730dba3..911e282305 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -1327,11 +1327,6 @@ (fn [cofx [_ public-key]] (contact/add-contact cofx public-key))) -(handlers/register-handler-fx - :contact.ui/block-contact-pressed - (fn [cofx [_ public-key]] - (contact.block/block-contact-confirmation cofx public-key))) - (handlers/register-handler-fx :contact.ui/block-contact-confirmed (fn [cofx [_ public-key]] diff --git a/src/status_im/ui/components/animation.cljs b/src/status_im/ui/components/animation.cljs index 4338c55273..81f79d466b 100644 --- a/src/status_im/ui/components/animation.cljs +++ b/src/status_im/ui/components/animation.cljs @@ -42,8 +42,8 @@ (defn anim-delay [duration] (.delay react/animated duration)) -(defn event [config] - (.event react/animated (clj->js [nil, config]))) +(defn event [mapping config] + (.event react/animated (clj->js mapping) (clj->js config))) (defn add-listener [anim-value listener] (.addListener anim-value listener)) @@ -60,6 +60,12 @@ (defn create-value [value] (js/ReactNative.Animated.Value. value)) +(defn add [anim-x anim-y] + (js/ReactNative.Animated.add. anim-x anim-y)) + +(defn subtract [anim-x anim-y] + (js/ReactNative.Animated.subtract. anim-x anim-y)) + (defn x [value-xy] (.-x value-xy)) diff --git a/src/status_im/ui/components/large_toolbar.cljs b/src/status_im/ui/components/large_toolbar.cljs deleted file mode 100644 index 3e4ba15ffb..0000000000 --- a/src/status_im/ui/components/large_toolbar.cljs +++ /dev/null @@ -1,151 +0,0 @@ -(ns status-im.ui.components.large-toolbar - (:require-macros [status-im.utils.views :as views]) - (:require [cljs-bean.core :refer [->clj ->js]] - [reagent.core :as reagent] - [status-im.ui.components.animation :as animation] - [status-im.ui.components.colors :as colors] - [status-im.ui.components.list.views :as list.views] - [status-im.ui.components.list-item.views :as list-item] - [status-im.ui.components.react :as react] - [status-im.ui.components.toolbar.styles :as toolbar.styles] - [status-im.ui.components.toolbar.view :as toolbar] - [status-im.utils.platform :as platform])) - -(def hidden (reagent/atom 0)) -(def shown (reagent/atom 100)) -(def minimized-header-visible? (reagent/atom false)) -(def initial-on-show-done? (volatile! false)) - -(defn animated-content-wrapper [header-in-toolbar has-nav? show?] - (let [anim-opacity (animation/create-value 0) - to-hide (reagent/atom false)] - (reagent/create-class - {:component-did-update - (fn [comp] - (let [new-argv (rest (reagent/argv comp)) - show? (last new-argv)] - (cond - (and (not @to-hide) show?) - (animation/start - (animation/timing - anim-opacity - {:toValue 1 - :duration 200 - :easing (.-ease (animation/easing)) - :useNativeDriver true}) - #(reset! to-hide true)) - - (and @to-hide (not show?)) - (animation/start - (animation/timing - anim-opacity - {:toValue 0 - :duration 200 - :easing (.-ease (animation/easing)) - :useNativeDriver true}) - #(reset! to-hide false))))) - - :reagent-render - (fn [header-in-toolbar has-nav? _] - [react/animated-view - {:style (cond-> {:flex 1 - :align-self :stretch - :opacity anim-opacity} - (false? has-nav?) - (assoc :margin-left -40 :margin-right 40))} - header-in-toolbar])}))) - -(defn on-viewable-items-changed [threshold interporlation-step] - (fn [info] - (let [changed (->> (->clj info) - :changed - (filter #(= 1 (:index %)))) - viewable? (when (seq changed) - (->> changed - first - :isViewable))] - (when (and @initial-on-show-done? (not (nil? viewable?))) - (if (= threshold 0) - (if viewable? - (reset! minimized-header-visible? false) - (reset! minimized-header-visible? true)) - (if viewable? - (do (swap! hidden - interporlation-step) (swap! shown + interporlation-step)) - (do (swap! hidden + interporlation-step) (swap! shown - interporlation-step)))))))) - -(defonce viewability-config-callback-pairs - (let [interporlation-step 20] - (->js - (vec - (for [threshold (range 0 (+ 100 interporlation-step) interporlation-step)] - {:viewabilityConfig {:itemVisiblePercentThreshold threshold} - :onViewableItemsChanged (on-viewable-items-changed threshold interporlation-step)}))))) - -;; header-in-toolbar - component - small header in toolbar -;; nav-item - component/nil - if nav-item like back button is needed, else nil -;; action-items - status-im.ui.components.toolbar.view/actions -(defn minimized-toolbar [header-in-toolbar nav-item action-items] - (let [has-nav? (boolean nav-item)] - [toolbar/toolbar - {:transparent? true - :style {:z-index 100 - :elevation 9}} - nav-item - [animated-content-wrapper header-in-toolbar has-nav? @minimized-header-visible?] - action-items])) - -;; header - component that serves as large header without any top/bottom padding -;; top(4px high) and bottom(16px high and with border) padding -;; are assumed to be constant -;; this is wrapped with padding components and merged with content -;; content - vector - of the rest(from header) of the list components -;; wrapped header and content form the data prop of flat-list -;; list-ref - atom - a reference to flat-list for the purpose of invoking its -;; methods -(views/defview flat-list-with-large-header [header content list-ref] - (views/letsubs [window-width [:dimensions/window-width]] - {:component-did-mount #(do (reset! hidden 0) (reset! shown 100) - (reset! minimized-header-visible? false) - (vreset! initial-on-show-done? false))} - (let [header-top-padding [react/view {:height 4}] - ;; header bottom padding with border-bottom - ;; fades out as it approaches toolbar shadow - header-bottom [react/animated-view - {:style {:height 16 - :opacity (/ @shown 100) - :border-bottom-width 1 - :border-bottom-color colors/gray-lighter}}] - wrapped-data (into [header-top-padding header header-bottom] content) - status-bar-height (get platform/platform-specific :status-bar-default-height) - toolbar-shadow-component-height - (+ 50 toolbar.styles/toolbar-height (if (zero? status-bar-height) 50 status-bar-height))] - [react/view {:flex 1} - ;; toolbar shadow - [react/animated-view - {:style - (cond-> {:flex 1 - :align-self :stretch - :position :absolute - :height toolbar-shadow-component-height - :width window-width - :top (- toolbar-shadow-component-height) - :shadow-radius 8 - :shadow-offset {:width 0 :height 2} - :shadow-opacity 1 - :shadow-color "rgba(0, 9, 26, 0.12)" - :elevation (if (>= @hidden 40) (- (/ @hidden 10) 2) 0) - :background-color colors/white} - platform/ios? - (assoc :opacity (if (>= @hidden 40) (/ @hidden 100) 0)))}] - - [list.views/flat-list - {:style {:z-index -1} - :data wrapped-data - :initial-num-to-render 3 - :ref #(reset! list-ref %) - :render-fn list.views/flat-list-generic-render-fn - :key-fn (fn [item idx] (str idx)) - :on-scroll-begin-drag #(when (false? @initial-on-show-done?) - (vreset! initial-on-show-done? true)) - :viewabilityConfigCallbackPairs viewability-config-callback-pairs - :keyboard-should-persist-taps :handled}]]))) diff --git a/src/status_im/ui/components/large_toolbar/styles.cljs b/src/status_im/ui/components/large_toolbar/styles.cljs new file mode 100644 index 0000000000..1b3c8a804b --- /dev/null +++ b/src/status_im/ui/components/large_toolbar/styles.cljs @@ -0,0 +1,84 @@ +(ns status-im.ui.components.large-toolbar.styles + (:require [status-im.ui.components.colors :as colors] + [status-im.ui.components.animation :as animation] + [status-im.ui.components.toolbar.styles :as toolbar.styles] + [status-im.utils.platform :as platform])) + +(defonce toolbar-shadow-component-height + (let [status-bar-height (get platform/platform-specific :status-bar-default-height)] + (+ 50 toolbar.styles/toolbar-height (if (zero? status-bar-height) 50 status-bar-height)))) + +(defonce toolbar-statusbar-height + (+ (get platform/platform-specific :status-bar-default-height) toolbar.styles/toolbar-height)) + +(defn minimized-toolbar-fade-in [anim-opacity] + (animation/timing + anim-opacity + {:toValue 1 + :duration 200 + :easing (.-ease (animation/easing)) + :useNativeDriver true})) + +(defn minimized-toolbar-fade-out [anim-opacity] + (animation/timing + anim-opacity + {:toValue 0 + :duration 200 + :easing (.-ease (animation/easing)) + :useNativeDriver true})) + +(defn- ios-shadow-opacity-anim [scroll-y] + (if platform/ios? + (animation/interpolate scroll-y + {:inputRange [0 toolbar-statusbar-height] + :outputRange [0 1] + :extrapolate "clamp"}) + 0)) + +(defn- android-shadow-elevation-anim [scroll-y] + (if platform/android? + (animation/interpolate scroll-y + {:inputRange [0 toolbar-statusbar-height] + :outputRange [0 9] + :extrapolate "clamp"}) + 0)) + +(defn bottom-border-opacity-anim [scroll-y] + (animation/interpolate scroll-y + {:inputRange [0 toolbar-statusbar-height] + :outputRange [1 0] + :extrapolate "clamp"})) + +(defn animated-content-wrapper [anim-opacity] + {:flex 1 + :align-self :stretch + :opacity anim-opacity}) + +(def minimized-toolbar + {:z-index 100 + :elevation 9}) + +(defn flat-list-with-large-header-bottom [scroll-y] + {:height 16 + :opacity (bottom-border-opacity-anim scroll-y) + :border-bottom-width 1 + :border-bottom-color colors/gray-lighter}) + +(defn flat-list-with-large-header-shadow [window-width scroll-y] + (cond-> {:flex 1 + :align-self :stretch + :position :absolute + :height toolbar-shadow-component-height + :width window-width + :top (- toolbar-shadow-component-height) + :shadow-radius 8 + :shadow-offset {:width 0 :height 2} + :shadow-opacity 1 + :shadow-color "rgba(0, 9, 26, 0.12)" + :elevation (android-shadow-elevation-anim scroll-y) + :background-color colors/white} + platform/ios? + (assoc :opacity (ios-shadow-opacity-anim scroll-y)))) + +(def flat-list + {:z-index -1}) diff --git a/src/status_im/ui/components/large_toolbar/view.cljs b/src/status_im/ui/components/large_toolbar/view.cljs new file mode 100644 index 0000000000..c62aefe844 --- /dev/null +++ b/src/status_im/ui/components/large_toolbar/view.cljs @@ -0,0 +1,85 @@ +(ns status-im.ui.components.large-toolbar.view + (:require [reagent.core :as reagent] + [cljs-bean.core :refer [->clj ->js]] + [status-im.ui.components.list.views :as list.views] + [status-im.ui.components.react :as react] + [status-im.ui.components.toolbar.view :as toolbar] + [status-im.ui.components.large-toolbar.styles :as styles] + [status-im.utils.platform :as platform] + [status-im.ui.components.animation :as animation]) + (:require-macros [status-im.utils.views :as views])) + +;; header-in-toolbar - component - small header in toolbar +;; nav-item - component/nil - if nav-item like back button is needed, else nil +;; action-items - status-im.ui.components.toolbar.view/actions +(defn minimized-toolbar [header-in-toolbar nav-item action-items anim-opacity] + (let [has-nav? (boolean nav-item)] + [toolbar/toolbar + {:transparent? true + :style styles/minimized-toolbar} + nav-item + [react/animated-view + {:style (cond-> (styles/animated-content-wrapper anim-opacity) + (false? has-nav?) + (assoc :margin-left -40 :margin-right 40))} + header-in-toolbar] + action-items])) + +;; header - component that serves as large header without any top/bottom padding +;; top(4px high) and bottom(16px high and with border) padding +;; are assumed to be constant +;; this is wrapped with padding components and merged with content +;; content - vector - of the rest(from header) of the list components +;; wrapped header and content form the data prop of flat-list +;; list-ref - atom - a reference to flat-list for the purpose of invoking its +;; methods +;; scroll-y - animated value tracking the y scoll of the main content in flat-list-view +(views/defview flat-list-with-large-header [header content list-ref scroll-y] + (views/letsubs [window-width [:dimensions/window-width]] + (let [header-top-padding [react/view {:height 4}] + ;; header bottom padding with border-bottom + ;; fades out as it approaches toolbar shadow + header-bottom [react/animated-view + {:style (styles/flat-list-with-large-header-bottom scroll-y)}] + wrapped-data (into [header-top-padding header header-bottom] content)] + [react/view {:flex 1} + ;; toolbar shadow + [react/animated-view + {:style (styles/flat-list-with-large-header-shadow window-width scroll-y)}] + + [list.views/flat-list + {:style styles/flat-list + :data wrapped-data + :initial-num-to-render 3 + :ref #(when % (reset! list-ref (.getNode %))) + :render-fn list.views/flat-list-generic-render-fn + :key-fn (fn [item idx] (str idx)) + :scrollEventThrottle 16 + :on-scroll (animation/event + [{:nativeEvent {:contentOffset {:y scroll-y}}}] + {:useNativeDriver true}) + :keyboard-should-persist-taps :handled} + {:animated? true}]]))) + +(defn generate-view + "main function which generates views. + - it will generate and return back: + - minimized-toolbar + - flat-list-with-large-header" + [header-in-toolbar nav-item toolbar-action-items header content list-ref] + (let [to-hide (reagent/atom false) + anim-opacity (animation/create-value 0) + scroll-y (animation/create-value 0)] + (animation/add-listener scroll-y (fn [anim] + (cond + (and (>= (.-value anim) 40) (not @to-hide)) + (animation/start + (styles/minimized-toolbar-fade-in anim-opacity) + #(reset! to-hide true)) + + (and (< (.-value anim) 40) @to-hide) + (animation/start + (styles/minimized-toolbar-fade-out anim-opacity) + #(reset! to-hide false))))) + {:minimized-toolbar [minimized-toolbar header-in-toolbar nav-item toolbar-action-items anim-opacity] + :content-with-header [flat-list-with-large-header header content list-ref scroll-y]})) \ No newline at end of file diff --git a/src/status_im/ui/components/list/views.cljs b/src/status_im/ui/components/list/views.cljs index ec16af01fd..2a77a3d470 100644 --- a/src/status_im/ui/components/list/views.cljs +++ b/src/status_im/ui/components/list/views.cljs @@ -223,13 +223,15 @@ (defn flat-list "A wrapper for FlatList. See https://facebook.github.io/react-native/docs/flatlist.html" - [{:keys [data] :as props}] - {:pre [(or (nil? data) - (sequential? data))]} - [flat-list-class - (merge (base-list-props props) - props - {:data (wrap-data data)})]) + ([props] (flat-list props nil)) + ([{:keys [data] :as props} {:keys [animated?]}] + (let [class (if animated? react/animated-flat-list-class flat-list-class)] + {:pre [(or (nil? data) + (sequential? data))]} + [class + (merge (base-list-props props) + props + {:data (wrap-data data)})]))) (defn flat-list-generic-render-fn "A generic status-react specific `render-fn` for `list-item`. @@ -283,13 +285,14 @@ [react/touchable-highlight {:on-press action} [react/view {:accessibility-label accessibility-label} [item - [item-icon {:icon icon - :style (merge styles/action - action-style - (when disabled? styles/action-disabled)) - :icon-opts (merge {:color :white} - icon-opts - (when disabled? {:color colors/gray}))}] + (when icon + [item-icon {:icon icon + :style (merge styles/action + action-style + (when disabled? styles/action-disabled)) + :icon-opts (merge {:color :white} + icon-opts + (when disabled? {:color colors/gray}))}]) (if-not subtext [item-primary-only {:style (merge styles/action-label (action-label-style false) diff --git a/src/status_im/ui/screens/profile/components/sheets.cljs b/src/status_im/ui/screens/profile/components/sheets.cljs new file mode 100644 index 0000000000..f45fc1a0da --- /dev/null +++ b/src/status_im/ui/screens/profile/components/sheets.cljs @@ -0,0 +1,44 @@ +(ns status-im.ui.screens.profile.components.sheets + (:require [re-frame.core :as re-frame] + [status-im.ui.components.react :as react] + [status-im.i18n :as i18n] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.list-item.views :as list-item] + [status-im.ui.screens.profile.components.styles :as styles]) + (:require-macros [status-im.utils.views :as views])) + +(defn hide-sheet-and-dispatch [event] + (re-frame/dispatch [:bottom-sheet/hide-sheet]) + (re-frame/dispatch event)) + +(views/defview add-contact [] + (views/letsubs [{:keys [public-key]} [:bottom-sheet/options]] + [react/view + [react/text {:style styles/sheet-text} + (i18n/label :t/add-to-contacts-text)] + [list-item/list-item + {:theme :action + :title :t/add-to-contacts + :icon :main-icons/add-contact + :on-press #(hide-sheet-and-dispatch [:contact.ui/add-to-contact-pressed public-key])}]])) + +(views/defview remove-contact [] + (views/letsubs [contact [:bottom-sheet/options]] + [react/view + [react/text {:style styles/sheet-text} + (i18n/label :t/remove-from-contacts-text)] + [list-item/list-item + {:theme :action-destructive + :title :t/remove-from-contacts + :icon :main-icons/remove-contact + :on-press #(hide-sheet-and-dispatch [:contact.ui/remove-contact-pressed contact])}]])) + +(views/defview block-contact [] + (views/letsubs [{:keys [public-key]} [:bottom-sheet/options]] + [react/view + [react/text {:style styles/sheet-text} + (i18n/label :t/block-contact-details)] + [list-item/list-item + {:theme :action-destructive + :title :t/block-contact + :on-press #(hide-sheet-and-dispatch [:contact.ui/block-contact-confirmed public-key])}]])) \ No newline at end of file diff --git a/src/status_im/ui/screens/profile/components/styles.cljs b/src/status_im/ui/screens/profile/components/styles.cljs index a04d7e7693..d24b9957a1 100644 --- a/src/status_im/ui/screens/profile/components/styles.cljs +++ b/src/status_im/ui/screens/profile/components/styles.cljs @@ -113,3 +113,11 @@ (def profile-form {:padding-vertical 16}) + +;; sheets + +(def sheet-text + {:color colors/gray + :padding 24 + :line-height 22 + :font-size 15}) \ No newline at end of file diff --git a/src/status_im/ui/screens/profile/contact/styles.cljs b/src/status_im/ui/screens/profile/contact/styles.cljs index cdf42abb8f..4f6f220325 100644 --- a/src/status_im/ui/screens/profile/contact/styles.cljs +++ b/src/status_im/ui/screens/profile/contact/styles.cljs @@ -2,28 +2,6 @@ (:require-macros [status-im.utils.styles :refer [defstyle]]) (:require [status-im.ui.components.colors :as colors])) -(def network-info {:background-color :white}) - -(def profile-info-item - {:flex-direction :row - :align-items :center - :padding-left 16}) - -(defn profile-info-text-container [options] - {:flex 1 - :padding-right (if options 16 40)}) - -(def profile-info-title - {:color colors/gray - :font-size 14}) - -(defstyle profile-setting-spacing - {:ios {:height 10} - :android {:height 7}}) - -(def profile-setting-text - {:font-size 17}) - (def action-container {:background-color colors/white}) @@ -51,20 +29,14 @@ (def action-icon-opts {:color colors/blue}) -(def block-action - {:background-color colors/red-transparent-10 - :border-radius 50}) +(def block-action-label + {:color colors/red + :padding-top 26 + :margin-left 16}) -(defn block-action-label [with-subtext?] - {:color colors/red}) - -(def block-action-icon-opts - {:color colors/red}) - -(def profile-setting-text-empty - (merge profile-setting-text - {:color colors/gray})) - -(def contact-profile-info-container +(def contact-profile-details-container {:padding-top 26 :background-color colors/white}) + +(def contact-profile-detail-share-icon + {:color colors/gray-transparent-40}) diff --git a/src/status_im/ui/screens/profile/contact/views.cljs b/src/status_im/ui/screens/profile/contact/views.cljs index fe3cdaec98..a51db266d8 100644 --- a/src/status_im/ui/screens/profile/contact/views.cljs +++ b/src/status_im/ui/screens/profile/contact/views.cljs @@ -1,105 +1,145 @@ (ns status-im.ui.screens.profile.contact.views (:require [re-frame.core :as re-frame] + [reagent.core :as reagent] [status-im.i18n :as i18n] [status-im.ui.components.list.views :as list] + [status-im.utils.utils :as utils] + [status-im.utils.platform :as platform] + [status-im.ui.components.tabbar.styles :as tabs.styles] + [status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.react :as react] [status-im.ui.components.status-bar.view :as status-bar] + [status-im.ui.components.large-toolbar.view :as large-toolbar] [status-im.ui.components.toolbar.view :as toolbar] - [status-im.ui.screens.profile.components.styles :as profile.components.styles] [status-im.ui.screens.profile.components.views :as profile.components] [status-im.ui.screens.profile.contact.styles :as styles] - [status-im.utils.platform :as platform]) - (:require-macros [status-im.utils.views :refer [defview letsubs]])) - -(defn profile-contact-toolbar [] - [toolbar/toolbar {} - toolbar/default-nav-back - [toolbar/content-title ""]]) + [status-im.ui.components.list-item.views :as list-item] + [status-im.ui.components.chat-icon.screen :as chat-icon] + [status-im.ui.screens.profile.components.sheets :as sheets] + [status-im.ui.screens.chat.photos :as photos] + [status-im.multiaccounts.core :as multiaccounts]) + (:require-macros [status-im.utils.views :as views])) (defn actions [{:keys [public-key added? tribute-to-talk] :as contact}] (let [{:keys [tribute-status tribute-label]} tribute-to-talk] - (concat (if added? - [{:label (i18n/label :t/in-contacts) - :icon :main-icons/in-contacts - :disabled? true - :accessibility-label :in-contacts-button}] - [{:label (i18n/label :t/add-to-contacts) - :icon :main-icons/add-contact - :action #(re-frame/dispatch [:contact.ui/add-to-contact-pressed public-key]) - :accessibility-label :add-to-contacts-button}]) - [(cond-> {:label (i18n/label :t/send-message) + (concat [(cond-> {:label (i18n/label :t/send-message) :icon :main-icons/message :action #(re-frame/dispatch [:contact.ui/send-message-pressed {:public-key public-key}]) :accessibility-label :start-conversation-button} (not (#{:none :paid} tribute-status)) - (assoc :subtext tribute-label)) + (assoc :subtext tribute-label))] ;;TODO hide temporary for v1 - #_{:label (i18n/label :t/send-transaction) - :icon :main-icons/send - :action #(re-frame/dispatch [:profile/send-transaction public-key]) - :accessibility-label :send-transaction-button} - {:label (i18n/label :t/share-profile-link) - :icon :main-icons/share - :action #(re-frame/dispatch [:profile/share-profile-link public-key]) - :accessibility-label :share-profile-link}]))) + #_{:label (i18n/label :t/send-transaction) + :icon :main-icons/send + :action #(re-frame/dispatch [:profile/send-transaction public-key]) + :accessibility-label :send-transaction-button} + (if added? + [{:label (i18n/label :t/remove-from-contacts) + :icon :main-icons/remove-contact + :accessibility-label :in-contacts-button + :action #(re-frame/dispatch [:contact.ui/remove-contact-pressed contact])}] + ;; TODO sheets temporary disabled + ;:action #(re-frame/dispatch [:bottom-sheet/show-sheet + ; {:content sheets/remove-contact + ; :content-height 150} + ; contact]) + [{:label (i18n/label :t/add-to-contacts) + :icon :main-icons/add-contact + :accessibility-label :add-to-contacts-button + :action #(re-frame/dispatch [:contact.ui/add-to-contact-pressed public-key])}])))) + ;; TODO sheets temporary disabled + ;:action #(re-frame/dispatch [:bottom-sheet/show-sheet + ; {:content sheets/add-contact + ; :content-height 150} + ; contact]) -(defn profile-info-item [{:keys [label value options accessibility-label]}] - [react/view styles/profile-info-item - [react/view (styles/profile-info-text-container options) - [react/text {:style styles/profile-info-title} - label] - [react/view styles/profile-setting-spacing] - [react/text {:style styles/profile-setting-text - :accessibility-label accessibility-label - :selectable true} - value]]]) - -(defn profile-info-contact-code-item [public-key] - [profile-info-item - {:label (i18n/label :t/contact-code) +(defn render-detail [{:keys [name public-key] :as detail}] + [list-item/list-item + {:title name + :subtitle (utils/get-shortened-address public-key) + :icon [chat-icon/contact-icon-contacts-tab detail] :accessibility-label :profile-public-key - :value public-key}]) + :on-press #(re-frame/dispatch [:show-popover {:view :share-chat-key :address public-key}]) + :accessories [[icons/icon :main-icons/share styles/contact-profile-detail-share-icon]]}]) -(defn profile-info [{:keys [public-key]}] +(defn profile-details-list-view [contact] + [list/flat-list {:data [contact] + :default-separator? true + :key-fn :public-key + :render-fn render-detail}]) + +(defn profile-details [contact] [react/view - [profile-info-contact-code-item public-key]]) + [list-item/list-item {:type :section-header + :title :t/profile-details + :title-accessibility-label :profile-details}] + [profile-details-list-view contact]]) -(defn block-contact-action [{:keys [blocked? public-key]}] - [list/render-action - {:label (if blocked? - (i18n/label :t/unblock-contact) - (i18n/label :t/block-contact)) - :icon :main-icons/cancel - :action (if blocked? - #(re-frame/dispatch [:contact.ui/unblock-contact-pressed public-key]) - #(re-frame/dispatch [:contact.ui/block-contact-pressed public-key])) - :accessibility-label (if blocked? - :unblock-contact - :block-contact)} - {:action-style styles/block-action - :action-label-style styles/block-action-label - :icon-opts styles/block-action-icon-opts}]) +(defn block-contact-action [{:keys [blocked? public-key] :as contact}] + [react/touchable-highlight {:on-press (if blocked? + #(re-frame/dispatch [:contact.ui/unblock-contact-pressed public-key]) + #(re-frame/dispatch [:bottom-sheet/show-sheet + {:content sheets/block-contact + :content-height 160} + contact]))} + [react/text {:style styles/block-action-label + :accessibility-label (if blocked? + :unblock-contact + :block-contact)} + (if blocked? + (i18n/label :t/unblock-contact) + (i18n/label :t/block-contact))]]) -(defview profile [] - (letsubs [contact [:contacts/current-contact]] - [react/view profile.components.styles/profile - [status-bar/status-bar] - [profile-contact-toolbar] - [react/scroll-view - [react/view profile.components.styles/profile-form - [profile.components/profile-header - {:contact contact - :editing? false - :allow-icon-change? false}]] - [list/action-list (actions contact) - {:container-style styles/action-container - :action-style styles/action - :action-label-style styles/action-label - :action-subtext-style styles/action-subtext - :action-separator-style styles/action-separator - :icon-opts styles/action-icon-opts}] - [react/view {:style {:height 16}}] - [block-contact-action contact] - [react/view styles/contact-profile-info-container - [profile-info contact]]]])) +(defn- header-in-toolbar [{:keys [photo-path] :as account}] + (let [displayed-name (multiaccounts/displayed-name account)] + [react/view {:flex 1 + :flex-direction :row + :align-items :center + :align-self :stretch} + [photos/photo photo-path {:size 40}] + [react/text {:style {:typography :title-bold + :line-height 21 + :margin-right 40 + :margin-left 16 + :text-align :left} + :accessibility-label :account-name} + displayed-name]])) + +(defn- header [account] + [profile.components/profile-header + {:contact account + :allow-icon-change? false + :include-remove-action? false}]) + +(views/defview profile [] + (views/letsubs [list-ref (reagent/atom nil) + contact [:contacts/current-contact]] + (let [header-in-toolbar (header-in-toolbar contact) + header (header contact) + content + [[list/action-list (actions contact) + {:container-style styles/action-container + :action-style styles/action + :action-label-style styles/action-label + :action-subtext-style styles/action-subtext + :action-separator-style styles/action-separator + :icon-opts styles/action-icon-opts}] + [react/view styles/contact-profile-details-container + [profile-details contact]] + [block-contact-action contact]] + generated-view (large-toolbar/generate-view + header-in-toolbar + toolbar/default-nav-back + nil + header + content + list-ref)] + [react/safe-area-view + {:style + (merge {:flex 1} + (when platform/ios? + {:margin-bottom tabs.styles/tabs-diff}))} + [status-bar/status-bar {:type :main}] + (:minimized-toolbar generated-view) + (:content-with-header generated-view)]))) \ No newline at end of file diff --git a/src/status_im/ui/screens/profile/user/views.cljs b/src/status_im/ui/screens/profile/user/views.cljs index ac8e5edf41..5ef370700c 100644 --- a/src/status_im/ui/screens/profile/user/views.cljs +++ b/src/status_im/ui/screens/profile/user/views.cljs @@ -9,7 +9,8 @@ [status-im.ui.components.colors :as colors] [status-im.ui.components.common.common :as components.common] [status-im.ui.components.copyable-text :as copyable-text] - [status-im.ui.components.large-toolbar :as large-toolbar] + [status-im.ui.components.large-toolbar.view :as large-toolbar] + [status-im.ui.components.list-item.views :as list-item] [status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list.views :as list.views] [status-im.ui.components.qr-code-viewer.views :as qr-code-viewer] @@ -203,18 +204,30 @@ registrar [:ens.stateofus/registrar]] (let [show-backup-seed? (and (not seed-backed-up?) (not (string/blank? mnemonic))) + + ;; toolbar-contents + header-in-toolbar (header-in-toolbar multiaccount) + toolbar-action-items (toolbar-action-items public-key) + + ;; flatlist contents + header (header multiaccount) content (flat-list-content preferred-name registrar tribute-to-talk - active-contacts-count show-backup-seed?)] + active-contacts-count show-backup-seed?) + + ;; generated toolbar and content with header + generated-view (large-toolbar/generate-view + header-in-toolbar + nil + toolbar-action-items + header + content + list-ref)] [react/safe-area-view {:style (merge {:flex 1} (when platform/ios? {:margin-bottom tabs.styles/tabs-diff}))} [status-bar/status-bar {:type :main}] - [large-toolbar/minimized-toolbar - (header-in-toolbar multiaccount) - nil - (toolbar-action-items public-key)] - [large-toolbar/flat-list-with-large-header - (header multiaccount) content list-ref]]))) + (:minimized-toolbar generated-view) + (:content-with-header generated-view)]))) diff --git a/test/cljs/status_im/react_native/js_dependencies.cljs b/test/cljs/status_im/react_native/js_dependencies.cljs index 76b8f0b1c1..0a11242d5e 100644 --- a/test/cljs/status_im/react_native/js_dependencies.cljs +++ b/test/cljs/status_im/react_native/js_dependencies.cljs @@ -16,8 +16,9 @@ (def react-native #js {:NativeModules #js {} - :Animated #js {:View #js {} - :Text #js {}} + :Animated #js {:View #js {} + :FlatList #js {} + :Text #js {}} :DeviceEventEmitter #js {:addListener (fn [])} :Dimensions #js {:get (fn [])}}) diff --git a/translations/en.json b/translations/en.json index c3005164b1..8616ffa0a3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -26,6 +26,7 @@ "add-members": "Add members", "add-network": "Add network", "add-to-contacts": "Add to contacts", + "add-to-contacts-text": "By adding a user to your contact list, you share your wallet address", "address": "Address", "advanced": "Advanced", "advanced-settings": "Advanced settings", @@ -70,7 +71,7 @@ "blank-keycard-text": "You can proceed with your keycard once you've generated your keys and name", "blank-keycard-title": "Looks like you’ve tapped \na blank keycard", "block": "Block", - "block-contact": "Block contact", + "block-contact": "Block this user", "block-contact-details": "Blocking will delete this user's previous messages and stop new ones from reaching you", "blocked-users": "Blocked users", "bootnode-address": "Bootnode address", @@ -782,6 +783,7 @@ "processing": "Processing", "product-information": "Product Information", "profile": "Profile", + "profile-details": "Profile details", "public-chat": "Public chat", "public-chats": "Public chats", "public-group-status": "Public", @@ -825,6 +827,8 @@ "remind-me-later": "Remind me later", "remove": "Remove", "remove-from-chat": "Remove from chat", + "remove-from-contacts": "Remove from contacts", + "remove-from-contacts-text": "By removing a user from your contact list you do not hide your wallet address from them", "remove-network": "Remove network", "remove-token": "Remove token", "removed": "removed", @@ -1007,7 +1011,7 @@ "type-a-message": "Type a message...", "ulc-enabled": "ULC enabled", "unable-to-read-this-code": "Unable to read this code", - "unblock-contact": "Unblock contact", + "unblock-contact": "Unblock this user", "unknown-status-go-error": "Unknown status-go error", "unlock": "Unlock", "unpair-card": "Unpair card",