From 69a84c8315097000a3c096ec7b827ea04aa7a40c Mon Sep 17 00:00:00 2001 From: Eric Dvorsak Date: Wed, 9 Aug 2017 12:48:28 +0200 Subject: [PATCH] Costmetic refactoring according to refactoring guidelines --- src/status_im/android/core.cljs | 6 +- src/status_im/components/drawer/view.cljs | 6 +- src/status_im/ios/core.cljs | 4 +- src/status_im/profile/edit/screen.cljs | 113 --------- src/status_im/profile/handlers.cljs | 62 ----- .../profile/photo_capture/styles.cljs | 5 - src/status_im/profile/qr_code/screen.cljs | 58 ----- src/status_im/profile/screen.cljs | 232 ------------------ src/status_im/profile/specs.cljs | 5 - src/status_im/profile/validations.cljs | 25 -- src/status_im/ui/screens/db.cljs | 3 +- src/status_im/ui/screens/events.cljs | 150 ++++++----- src/status_im/ui/screens/profile/db.cljs | 28 +++ .../ui/screens/profile/edit/views.cljs | 106 ++++++++ src/status_im/ui/screens/profile/events.cljs | 58 +++++ .../ui/screens/profile/navigation.cljs | 9 + .../screens/profile/photo_capture/styles.cljs | 5 + .../screens/profile/photo_capture/views.cljs} | 55 ++--- .../screens}/profile/qr_code/styles.cljs | 5 +- .../ui/screens/profile/qr_code/views.cljs | 50 ++++ .../{ => ui/screens}/profile/styles.cljs | 28 +-- src/status_im/ui/screens/profile/views.cljs | 221 +++++++++++++++++ src/status_im/ui/screens/views.cljs | 10 +- src/status_im/utils/handlers.cljs | 38 +-- 24 files changed, 624 insertions(+), 658 deletions(-) delete mode 100644 src/status_im/profile/edit/screen.cljs delete mode 100644 src/status_im/profile/handlers.cljs delete mode 100644 src/status_im/profile/photo_capture/styles.cljs delete mode 100644 src/status_im/profile/qr_code/screen.cljs delete mode 100644 src/status_im/profile/screen.cljs delete mode 100644 src/status_im/profile/specs.cljs delete mode 100644 src/status_im/profile/validations.cljs create mode 100644 src/status_im/ui/screens/profile/db.cljs create mode 100644 src/status_im/ui/screens/profile/edit/views.cljs create mode 100644 src/status_im/ui/screens/profile/events.cljs create mode 100644 src/status_im/ui/screens/profile/navigation.cljs create mode 100644 src/status_im/ui/screens/profile/photo_capture/styles.cljs rename src/status_im/{profile/photo_capture/screen.cljs => ui/screens/profile/photo_capture/views.cljs} (50%) rename src/status_im/{ => ui/screens}/profile/qr_code/styles.cljs (93%) create mode 100644 src/status_im/ui/screens/profile/qr_code/views.cljs rename src/status_im/{ => ui/screens}/profile/styles.cljs (85%) create mode 100644 src/status_im/ui/screens/profile/views.cljs diff --git a/src/status_im/android/core.cljs b/src/status_im/android/core.cljs index 018ee70d11..2dee8640a2 100644 --- a/src/status_im/android/core.cljs +++ b/src/status_im/android/core.cljs @@ -45,8 +45,8 @@ (let [o (orientation->keyword (.getInitialOrientation react/orientation))] (dispatch [:set :orientation o])) (.addOrientationListener - react/orientation - #(dispatch [:set :orientation (orientation->keyword %)])) + react/orientation + #(dispatch [:set :orientation (orientation->keyword %)])) (.lockToPortrait react/orientation) (.addListener react/keyboard "keyboardDidShow" @@ -74,4 +74,4 @@ (.registerComponent react/app-registry "StatusIm" #(reagent/reactify-component app-root)) (status/set-soft-input-mode status/adjust-resize) (init-back-button-handler!) - (dispatch-sync [:initialize-app])) \ No newline at end of file + (dispatch-sync [:initialize-app])) diff --git a/src/status_im/components/drawer/view.cljs b/src/status_im/components/drawer/view.cljs index c894f1bab5..5de4f3a40f 100644 --- a/src/status_im/components/drawer/view.cljs +++ b/src/status_im/components/drawer/view.cljs @@ -22,7 +22,7 @@ dismiss-keyboard!]] [status-im.components.status-view.view :as status-view] [status-im.i18n :as i18n] - [status-im.profile.validations :as v] + [status-im.ui.screens.profile.db :as profile.db] [status-im.utils.datetime :as time] [status-im.utils.gfycat.core :as gfycat] [status-im.utils.listview :as lw] @@ -54,13 +54,13 @@ [view {:style st/name-input-wrapper} [text-input {:placeholder placeholder - :style (st/name-input-text (s/valid? ::v/name (or new-name current-name))) + :style (st/name-input-text (s/valid? ::profile.db/name (or new-name current-name))) :font :medium :default-value (or new-name current-name) :on-change-text #(rf/dispatch [:set-in [:profile-edit :name] %]) :on-end-editing #(do (rf/dispatch [:set-in [:profile-edit :name] nil]) - (when (s/valid? ::v/name new-name) + (when (s/valid? ::profile.db/name new-name) (rf/dispatch [:account-update {:name (utils/clean-text new-name)}])))}]])) (defview status-input [] diff --git a/src/status_im/ios/core.cljs b/src/status_im/ios/core.cljs index bbb63508c5..97532bd821 100644 --- a/src/status_im/ios/core.cljs +++ b/src/status_im/ios/core.cljs @@ -21,8 +21,8 @@ (let [o (orientation->keyword (.getInitialOrientation react/orientation))] (dispatch [:set :orientation o])) (.addOrientationListener - react/orientation - #(dispatch [:set :orientation (orientation->keyword %)])) + react/orientation + #(dispatch [:set :orientation (orientation->keyword %)])) (.lockToPortrait react/orientation) (.addListener react/keyboard "keyboardWillShow" diff --git a/src/status_im/profile/edit/screen.cljs b/src/status_im/profile/edit/screen.cljs deleted file mode 100644 index d4d07a8c11..0000000000 --- a/src/status_im/profile/edit/screen.cljs +++ /dev/null @@ -1,113 +0,0 @@ -(ns status-im.profile.edit.screen - (:require-macros [status-im.utils.views :refer [defview]]) - (:require [cljs.spec.alpha :as s] - [clojure.string :as str] - [reagent.core :as r] - [re-frame.core :refer [dispatch]] - [status-im.profile.styles :as st] - [status-im.components.text-input-with-label.view :refer [text-input-with-label]] - [status-im.components.styles :refer [color-blue color-gray5]] - [status-im.components.status-bar :refer [status-bar]] - [status-im.components.toolbar-new.view :refer [toolbar]] - [status-im.components.toolbar-new.actions :as act] - [status-im.i18n :refer [label]] - [status-im.profile.screen :refer [colorize-status-hashtags]] - [status-im.components.sticky-button :refer [sticky-button]] - [status-im.components.camera :as camera] - [status-im.components.chat-icon.screen :refer [my-profile-icon]] - [status-im.components.context-menu :refer [context-menu]] - [status-im.profile.validations :as v] - [status-im.components.react :refer [view - scroll-view - keyboard-avoiding-view - text - touchable-highlight - text-input]] - [status-im.utils.utils :as utils :refer [clean-text]] - [status-im.utils.platform :refer [ios?]])) - -(defn edit-my-profile-toolbartoolbar [] - [toolbar {:title (label :t/edit-profile) - :actions [{:image :blank}]}]) - -(defview profile-name-input [] - [new-profile-name [:get-in [:profile-edit :name]]] - [view - [text-input-with-label {:label (label :t/name) - :default-value new-profile-name - :on-change-text #(dispatch [:set-in [:profile-edit :name] %])}]]) - -(def profile-icon-options - [{:text (label :t/image-source-gallery) - :value #(dispatch [:open-image-picker])} - {:text (label :t/image-source-make-photo) - :value (fn [] - (dispatch [:request-permissions - [:camera :write-external-storage] - (fn [] - (camera/request-access - #(if % (dispatch [:navigate-to :profile-photo-capture]) - (utils/show-popup (label :t/error) - (label :t/camera-access-error)))))]))}]) - -(defn edit-profile-bage [contact] - [view st/edit-profile-bage - [view st/edit-profile-icon-container - [context-menu - [my-profile-icon {:account contact - :edit? true}] - profile-icon-options - st/context-menu-custom-styles]] - [view st/edit-profile-name-container - [profile-name-input]]]) - -(defn edit-profile-status [{:keys [status edit-status?]}] - (let [input-ref (r/atom nil)] - [view st/edit-profile-status - [scroll-view - (if edit-status? - [text-input - {:ref #(reset! input-ref %) - :auto-focus edit-status? - :multiline true - :max-length 140 - :placeholder (label :t/status) - :style st/profile-status-input - :on-change-text #(dispatch [:set-in [:profile-edit :status] (clean-text %)]) - :on-blur #(dispatch [:set-in [:profile-edit :edit-status?] false]) - :blur-on-submit true - :on-submit-editing #(.blur @input-ref) - :default-value status}] - [touchable-highlight {:on-press #(dispatch [:set-in [:profile-edit :edit-status?] true])} - [view - (if (str/blank? status) - [text {:style st/add-a-status} - (label :t/status)] - [text {:style st/profile-status-text} - (colorize-status-hashtags status)])]])]])) - -(defn status-prompt [{:keys [status]}] - (when (or (nil? status) (str/blank? status)) - [view st/status-prompt - [text {:style st/status-prompt-text} - (colorize-status-hashtags (label :t/status-prompt))]])) - -(defview edit-my-profile [] - [current-account [:get-current-account] - changed-account [:get :profile-edit]] - {:component-will-unmount #(dispatch [:set-in [:profile-edit :edit-status?] false])} - (let [profile-edit-data-valid? (s/valid? ::v/profile changed-account) - profile-edit-data-changed? (or (not= (:name current-account) (:name changed-account)) - (not= (:status current-account) (:status changed-account)) - (not= (:photo-path current-account) (:photo-path changed-account)))] - [keyboard-avoiding-view {:style st/profile} - [status-bar] - [edit-my-profile-toolbartoolbar] - [view st/edit-my-profile-form - [edit-profile-bage changed-account] - [edit-profile-status changed-account] - [status-prompt changed-account]] - (when (and profile-edit-data-changed? profile-edit-data-valid?) - [sticky-button (label :t/save) #(do - (dispatch [:check-status-change (:status changed-account)]) - (dispatch [:account-update changed-account]))])])) diff --git a/src/status_im/profile/handlers.cljs b/src/status_im/profile/handlers.cljs deleted file mode 100644 index f9c7731800..0000000000 --- a/src/status_im/profile/handlers.cljs +++ /dev/null @@ -1,62 +0,0 @@ -(ns status-im.profile.handlers - (:require [re-frame.core :refer [subscribe dispatch after]] - [status-im.utils.handlers :refer [register-handler get-hashtags] :as u] - [status-im.components.react :refer [show-image-picker]] - [status-im.utils.image-processing :refer [img->base64]] - [status-im.i18n :refer [label]] - [taoensso.timbre :as log] - [status-im.constants :refer [console-chat-id]] - [status-im.ui.screens.navigation :as nav])) - -(defn message-user [identity] - (when identity - (dispatch [:navigation-replace :chat identity]))) - -(register-handler :open-image-picker - (u/side-effect! - (fn [_ _] - (show-image-picker - (fn [image] - (let [path (get (js->clj image) "path") - _ (log/debug path) - on-success (fn [base64] - (dispatch [:set-in [:profile-edit :photo-path] (str "data:image/jpeg;base64," base64)])) - on-error (fn [type error] - (.log js/console type error))] - (img->base64 path on-success on-error))))))) - -(register-handler :phone-number-change-requested - ;; Switch user to the console issuing the !phone command automatically to let him change his phone number. - ;; We allow to change phone number only from console because this requires entering SMS verification code. - (u/side-effect! - (fn [db _] - (dispatch [:navigate-to :chat console-chat-id]) - (js/setTimeout #(dispatch [:select-chat-input-command {:name "phone"}]) 500)))) - -(register-handler :open-chat-with-the-send-transaction - (u/side-effect! - (fn [db [_ chat-id]] - (dispatch [:clear-seq-arguments]) - (dispatch [:navigate-to :chat chat-id]) - (js/setTimeout #(dispatch [:select-chat-input-command {:name "send"}]) 500)))) - -(defn prepare-edit-profile - [{:keys [current-account-id] :as db} _] - (let [current-account (select-keys (get-in db [:accounts current-account-id]) - [:name :photo-path :status])] - (update-in db [:profile-edit] merge current-account))) - -(defn open-edit-profile [_ _] - (dispatch [:navigate-to :edit-my-profile])) - -(register-handler :open-edit-my-profile - (u/handlers-> - prepare-edit-profile - open-edit-profile)) - -(defmethod nav/preload-data! :qr-code-view - [{:keys [current-account-id] :as db} [_ _ {:keys [contact qr-source amount?]}]] - (assoc db :qr-modal {:contact (or contact - (get-in db [:accounts current-account-id])) - :qr-source qr-source - :amount? amount?})) diff --git a/src/status_im/profile/photo_capture/styles.cljs b/src/status_im/profile/photo_capture/styles.cljs deleted file mode 100644 index efd151170c..0000000000 --- a/src/status_im/profile/photo_capture/styles.cljs +++ /dev/null @@ -1,5 +0,0 @@ -(ns status-im.profile.photo-capture.styles) - -(def container - {:flex 1 - :background-color :white}) \ No newline at end of file diff --git a/src/status_im/profile/qr_code/screen.cljs b/src/status_im/profile/qr_code/screen.cljs deleted file mode 100644 index 4c961bcfa7..0000000000 --- a/src/status_im/profile/qr_code/screen.cljs +++ /dev/null @@ -1,58 +0,0 @@ -(ns status-im.profile.qr-code.screen - (:require-macros [status-im.utils.views :refer [defview]]) - (:require [status-im.components.react :refer [view - text - image - icon - touchable-highlight - get-dimensions]] - [status-im.components.status-bar :refer [status-bar]] - [status-im.components.styles :refer [icon-close]] - [status-im.components.qr-code :refer [qr-code]] - [re-frame.core :refer [dispatch subscribe]] - [status-im.profile.qr-code.styles :as st] - [status-im.i18n :refer [label]] - [clojure.string :as s])) - -(defview qr-code-view [] - [{:keys [photo-path address name] :as contact} [:get-in [:qr-modal :contact]] - {:keys [qr-source amount? dimensions]} [:get :qr-modal] - {:keys [amount]} [:get :contacts/click-params]] - [view st/wallet-qr-code - [status-bar {:type :modal}] - [view st/account-toolbar - [view st/wallet-account-container - [view st/qr-photo-container - [view st/account-photo-container - [image {:source {:uri (if (s/blank? photo-path) :avatar photo-path)} - :style st/photo-image}]]] - [view st/name-container - [text {:style st/name-text - :number-of-lines 1} name]] - [view st/online-container - [touchable-highlight {:onPress #(dispatch [:navigate-back])} - [view st/online-image-container - [icon :close_white]]]]]] - [view {:style st/qr-code - :on-layout #(let [layout (.. % -nativeEvent -layout)] - (dispatch [:set-in [:qr-modal :dimensions] {:width (.-width layout) - :height (.-height layout)}]))} - (when (:width dimensions) - [view {:style (st/qr-code-container dimensions)} - [qr-code {:value (if amount? - (prn-str {:address (get contact qr-source) - :amount amount}) - (str "ethereum:" (get contact qr-source))) - :size (- (min (:width dimensions) - (:height dimensions)) - 80)}]])] - [view st/footer - [view st/wallet-info - [text {:style st/wallet-name-text} (label :t/main-wallet)] - [text {:style st/wallet-address-text} address]] - - [touchable-highlight {:onPress #(dispatch [:navigate-back])} - [view st/done-button - [text {:style st/done-button-text} (label :t/done)]]]]]) - - diff --git a/src/status_im/profile/screen.cljs b/src/status_im/profile/screen.cljs deleted file mode 100644 index bd31536f6b..0000000000 --- a/src/status_im/profile/screen.cljs +++ /dev/null @@ -1,232 +0,0 @@ -(ns status-im.profile.screen - (:require-macros [status-im.utils.views :refer [defview]]) - (:require [re-frame.core :refer [dispatch]] - [clojure.string :as str] - [reagent.core :as r] - [status-im.ui.screens.contacts.styles :as cst] - [status-im.components.common.common :refer [separator - form-spacer - top-shadow - bottom-shadow]] - [status-im.components.styles :refer [color-blue color-gray5]] - [status-im.components.context-menu :refer [context-menu]] - [status-im.components.action-button.action-button :refer [action-button - action-button-disabled - action-separator]] - [status-im.components.action-button.styles :refer [actions-list]] - [status-im.components.react :refer [view - text - text-input - image - icon - scroll-view - touchable-highlight]] - [status-im.components.chat-icon.screen :refer [my-profile-icon]] - [status-im.components.status-bar :refer [status-bar]] - [status-im.components.toolbar-new.view :refer [toolbar]] - [status-im.components.toolbar-new.actions :as act] - [status-im.components.list-selection :refer [share-options]] - [status-im.utils.platform :refer [platform-specific android?]] - [status-im.profile.handlers :refer [message-user]] - [status-im.profile.styles :as st] - [status-im.i18n :refer [label]] - [status-im.utils.datetime :as time] - [status-im.utils.utils :refer [hash-tag?]])) - - -(defn my-profile-toolbar [] - [toolbar {:actions [(act/opts [{:value #(dispatch [:open-edit-my-profile]) - :text (label :t/edit)}])]}]) - -(defn profile-toolbar [contact] - [toolbar - (when (and (not (:pending? contact)) - (not (:unremovable? contact))) - {:actions [(act/opts [{:value #(dispatch [:hide-contact contact]) - :text (label :t/remove-from-contacts)}])]})]) - -(defn online-text [last-online] - (let [last-online-date (time/to-date last-online) - now-date (time/now)] - (if (and (pos? last-online) - (<= last-online-date now-date)) - (time/time-ago last-online-date) - (label :t/active-unknown)))) - -(defn profile-badge [{:keys [name last-online] :as contact}] - [view st/profile-bage - [my-profile-icon {:account contact - :edit? false}] - [view st/profile-badge-name-container - [text {:style st/profile-name-text - :number-of-lines 1} - name] - (when-not (nil? last-online) - [view st/profile-activity-status-container - [text {:style st/profile-activity-status-text} - (online-text last-online)]])]]) - -(defn profile-actions [{:keys [pending? whisper-identity dapp?]} chat-id] - [view actions-list - (if pending? - [action-button (label :t/add-to-contacts) - :add_blue - #(dispatch [:add-pending-contact chat-id])] - [action-button-disabled (label :t/in-contacts) - :ok_dark]) - [action-separator] - [action-button (label :t/start-conversation) - :chats_blue - #(message-user whisper-identity)] - (when-not dapp? - [view - [action-separator] - [action-button (label :t/send-transaction) - :arrow_right_blue - #(dispatch [:open-chat-with-the-send-transaction chat-id])]])]) - -(defn profile-info-item [{:keys [label value options text-mode empty-value?]}] - [view st/profile-setting-item - [view (st/profile-info-text-container options) - [text {:style st/profile-setting-title} - label] - [view st/profile-setting-spacing] - [text {:style (if empty-value? - st/profile-setting-text-empty - st/profile-setting-text) - :number-of-lines 1 - :ellipsizeMode text-mode} - value]] - (when options - [context-menu - [icon :options_gray] - options - nil - st/profile-info-item-button])]) - -(defn show-qr [contact qr-source] - #(dispatch [:navigate-to-modal :qr-code-view {:contact contact - :qr-source qr-source}])) - -(defn profile-options [contact k text] - (into [] - (concat [{:value (show-qr contact k) - :text (label :t/show-qr)}] - (when text - (share-options text))))) - -(defn profile-info-address-item [{:keys [address] :as contact}] - [profile-info-item - {:label (label :t/address) - :value address - :options (profile-options contact :address address) - :text-mode :middle}]) - -(defn profile-info-public-key-item [public-key contact] - [profile-info-item - {:label (label :t/public-key) - :value public-key - :options (profile-options contact :public-key public-key) - :text-mode :middle}]) - -(defn info-item-separator [] - [separator st/info-item-separator]) - -(defn tag-view [tag] - [text {:style {:color color-blue} - :font :medium} - (str tag " ")]) - -(defn colorize-status-hashtags [status] - (for [[i status] (map-indexed vector (str/split status #" "))] - (if (hash-tag? status) - ^{:key (str "item-" i)} - [tag-view status] - ^{:key (str "item-" i)} - (str status " ")))) - -(defn profile-info-phone-item [phone & [options]] - (let [phone-empty? (or (nil? phone) (str/blank? phone)) - phone-text (if phone-empty? - (label :t/not-specified) - phone)] - [profile-info-item {:label (label :t/phone-number) - :value phone-text - :options options - :empty-value? phone-empty?}])) - -(defn profile-info [{:keys [whisper-identity status phone] :as contact}] - [view - [profile-info-address-item contact] - [info-item-separator] - [profile-info-public-key-item whisper-identity contact] - [info-item-separator] - [profile-info-phone-item phone]]) - -(defn my-profile-info [{:keys [public-key status phone] :as contact}] - [view - [profile-info-address-item contact] - [info-item-separator] - [profile-info-public-key-item public-key contact] - [info-item-separator] - [profile-info-phone-item - phone - [{:value #(dispatch [:phone-number-change-requested]) - :text (label :t/edit)}]]]) - -(defn- profile-status-on-press [] - (dispatch [:set-in [:profile-edit :edit-status?] true]) - (dispatch [:open-edit-my-profile])) - -(defn profile-status [status & [edit?]] - [view st/profile-status-container - (if (or (nil? status) (str/blank? status)) - [touchable-highlight {:on-press profile-status-on-press} - [view - [text {:style st/add-a-status} - (label :t/add-a-status)]]] - [scroll-view - [touchable-highlight {:on-press (when edit? profile-status-on-press)} - [view - [text {:style st/profile-status-text} - (colorize-status-hashtags status)]]]])]) - -(defview my-profile [] - [{:keys [status] :as current-account} [:get-current-account]] - [view st/profile - [status-bar] - [my-profile-toolbar] - [scroll-view - [view st/profile-form - [profile-badge current-account] - [profile-status status true]] - [form-spacer] - [view actions-list - [action-button (label :t/show-qr) - :q_r_blue - (show-qr current-account :public-key)]] - [form-spacer] - [view st/profile-info-container - [my-profile-info current-account] - [bottom-shadow]]]]) - -(defview profile [] - [{:keys [pending? - status - whisper-identity] - :as contact} [:contact] - chat-id [:get :current-chat-id]] - [view st/profile - [status-bar] - [profile-toolbar contact] - [scroll-view - [view st/profile-form - [profile-badge contact] - (when (and (not (nil? status)) (not (str/blank? status))) - [profile-status status])] - [form-spacer] - [profile-actions contact chat-id] - [form-spacer] - [view st/profile-info-container - [profile-info contact] - [bottom-shadow]]]]) diff --git a/src/status_im/profile/specs.cljs b/src/status_im/profile/specs.cljs deleted file mode 100644 index 6f8503913f..0000000000 --- a/src/status_im/profile/specs.cljs +++ /dev/null @@ -1,5 +0,0 @@ -(ns status-im.profile.specs - (:require [cljs.spec.alpha :as s])) - -;EDIT PROFILE -(s/def :profile/profile-edit (s/nilable map?)) \ No newline at end of file diff --git a/src/status_im/profile/validations.cljs b/src/status_im/profile/validations.cljs deleted file mode 100644 index 73a2057fb1..0000000000 --- a/src/status_im/profile/validations.cljs +++ /dev/null @@ -1,25 +0,0 @@ -(ns status-im.profile.validations - (:require [cljs.spec.alpha :as s] - [status-im.constants :refer [console-chat-id wallet-chat-id]] - [status-im.chat.constants :as chat-consts] - [clojure.string :as str] - [status-im.utils.homoglyph :as h])) - -(defn correct-name? [username] - (when-let [username (some-> username (str/trim))] - (every? false? - [(str/blank? username) - (h/matches username console-chat-id) - (h/matches username wallet-chat-id) - (str/includes? username chat-consts/command-char) - (str/includes? username chat-consts/bot-char)]))) - -(defn correct-email? [email] - (let [pattern #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"] - (or (str/blank? email) - (and (string? email) (re-matches pattern email))))) - -(s/def ::name correct-name?) -(s/def ::email correct-email?) - -(s/def ::profile (s/keys :req-un [::name])) diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index e3e763ee8b..b37a5008ba 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -9,7 +9,7 @@ status-im.ui.screens.group.db status-im.chat.specs status-im.chat.new-public-chat.db - status-im.profile.specs + status-im.ui.screens.profile.db status-im.transactions.specs status-im.ui.screens.discover.db)) @@ -38,7 +38,6 @@ :sync-state :done :network :testnet}) - ;;;;GLOBAL ;;public key of current logged in account diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index 42010abc1f..7ddc1baf8f 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -1,39 +1,37 @@ (ns status-im.ui.screens.events - (:require - status-im.chat.handlers - status-im.ui.screens.group.chat-settings.events - status-im.ui.screens.navigation - status-im.ui.screens.contacts.events - status-im.ui.screens.discover.events - status-im.ui.screens.group.events - status-im.profile.handlers - status-im.commands.handlers.loading - status-im.commands.handlers.jail - status-im.ui.screens.qr-scanner.events - status-im.ui.screens.accounts.events - status-im.protocol.handlers - status-im.transactions.handlers - status-im.network.handlers - status-im.debug.handlers - status-im.bots.handlers + (:require status-im.bots.handlers + status-im.chat.handlers + status-im.commands.handlers.jail + status-im.commands.handlers.loading + status-im.debug.handlers + status-im.network.handlers + status-im.protocol.handlers + status-im.transactions.handlers + status-im.ui.screens.accounts.events + status-im.ui.screens.contacts.events + status-im.ui.screens.discover.events + status-im.ui.screens.group.chat-settings.events + status-im.ui.screens.group.events + status-im.ui.screens.navigation + status-im.ui.screens.profile.events + status-im.ui.screens.qr-scanner.events - [re-frame.core :refer [dispatch reg-fx]] - [status-im.utils.handlers :refer [register-handler-db register-handler-fx]] - [status-im.ui.screens.db :refer [app-db]] - [status-im.data-store.core :as data-store] - [taoensso.timbre :as log] - [status-im.utils.crypt :as crypt] - [status-im.components.status :as status] - [status-im.components.permissions :as permissions] - - [status-im.utils.types :as types] - [status-im.constants :refer [console-chat-id]] - [status-im.utils.instabug :as inst] - [status-im.utils.platform :as platform] - [status-im.js-dependencies :as dependencies] - [status-im.utils.utils :as utils] - [status-im.utils.config :as config] - [status-im.i18n :as i18n])) + [re-frame.core :refer [dispatch reg-fx]] + [status-im.components.status :as status] + [status-im.components.permissions :as permissions] + [status-im.constants :refer [console-chat-id]] + [status-im.data-store.core :as data-store] + [status-im.i18n :as i18n] + [status-im.js-dependencies :as dependencies] + [status-im.ui.screens.db :refer [app-db]] + [status-im.utils.config :as config] + [status-im.utils.crypt :as crypt] + [status-im.utils.handlers :refer [register-handler-db register-handler-fx]] + [status-im.utils.instabug :as inst] + [status-im.utils.platform :as platform] + [status-im.utils.types :as types] + [status-im.utils.utils :as utils] + [taoensso.timbre :as log])) ;;;; COFX @@ -48,33 +46,33 @@ ::initialize-crypt-fx (fn [] (crypt/gen-random-bytes - 1024 - (fn [{:keys [error buffer]}] - (if error - (log/error "Failed to generate random bytes to initialize sjcl crypto") - (->> (.toString buffer "hex") - (.toBits (.. dependencies/eccjs -sjcl -codec -hex)) - (.addEntropy (.. dependencies/eccjs -sjcl -random)))))))) + 1024 + (fn [{:keys [error buffer]}] + (if error + (log/error "Failed to generate random bytes to initialize sjcl crypto") + (->> (.toString buffer "hex") + (.toBits (.. dependencies/eccjs -sjcl -codec -hex)) + (.addEntropy (.. dependencies/eccjs -sjcl -random)))))))) (defn node-started [result] (log/debug "Started Node")) (defn move-to-internal-storage [] (status/move-to-internal-storage - (fn [] - (status/start-node node-started)))) + (fn [] + (status/start-node node-started)))) (reg-fx ::initialize-geth-fx (fn [] (status/should-move-to-internal-storage? - (fn [should-move?] - (if should-move? - (dispatch [:request-permissions - [:read-external-storage] - #(move-to-internal-storage) - #(dispatch [:move-to-internal-failure-message])]) - (status/start-node node-started)))))) + (fn [should-move?] + (if should-move? + (dispatch [:request-permissions + [:read-external-storage] + #(move-to-internal-storage) + #(dispatch [:move-to-internal-failure-message])]) + (status/start-node node-started)))))) (reg-fx ::status-module-initialized-fx @@ -101,8 +99,8 @@ (fn [] (when config/testfairy-enabled? (utils/show-popup - (i18n/label :testfairy-title) - (i18n/label :testfairy-message))))) + (i18n/label :testfairy-title) + (i18n/label :testfairy-message))))) ;;;; Handlers @@ -133,12 +131,12 @@ network-status network _]} :db} _] {::init-store nil :db (assoc app-db - :current-account-id nil - :contacts/contacts {} - :network-status network-status - :status-module-initialized? (or platform/ios? js/goog.DEBUG status-module-initialized?) - :status-node-started? status-node-started? - :network (or network :testnet))})) + :current-account-id nil + :contacts/contacts {} + :network-status network-status + :status-module-initialized? (or platform/ios? js/goog.DEBUG status-module-initialized?) + :status-node-started? status-node-started? + :network (or network :testnet))})) (register-handler-db :initialize-account-db @@ -174,14 +172,14 @@ :chat :accounts)] (merge - {:db (assoc db :view-id view - :navigation-stack (list view))} - (when (or (empty? accounts) open-console?) - {:dispatch-n (concat - [[:init-console-chat] - [:load-commands!]] - (when open-console? - [[:navigate-to :chat console-chat-id]]))}))))) + {:db (assoc db :view-id view + :navigation-stack (list view))} + (when (or (empty? accounts) open-console?) + {:dispatch-n (concat + [[:init-console-chat] + [:load-commands!]] + (when open-console? + [[:navigate-to :chat console-chat-id]]))}))))) (register-handler-fx :initialize-crypt @@ -247,19 +245,19 @@ (fn [] (let [watch-id (atom nil)] (.getCurrentPosition - navigator.geolocation - #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) - #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) - (clj->js {:enableHighAccuracy true :timeout 20000 :maximumAge 1000})) + navigator.geolocation + #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) + #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) + (clj->js {:enableHighAccuracy true :timeout 20000 :maximumAge 1000})) (when platform/android? (reset! watch-id (.watchPosition - navigator.geolocation - #(do - (.clearWatch - navigator.geolocation - @watch-id) - (dispatch [:update-geolocation (js->clj % :keywordize-keys true)])))))))]})) + navigator.geolocation + #(do + (.clearWatch + navigator.geolocation + @watch-id) + (dispatch [:update-geolocation (js->clj % :keywordize-keys true)])))))))]})) (register-handler-db :update-geolocation diff --git a/src/status_im/ui/screens/profile/db.cljs b/src/status_im/ui/screens/profile/db.cljs new file mode 100644 index 0000000000..d8b240e788 --- /dev/null +++ b/src/status_im/ui/screens/profile/db.cljs @@ -0,0 +1,28 @@ +(ns status-im.ui.screens.profile.db + (:require [cljs.spec.alpha :as spec] + [clojure.string :as string] + [status-im.chat.constants :as chat.constants] + [status-im.constants :as constants] + [status-im.utils.homoglyph :as homoglyph])) + +(defn correct-name? [username] + (when-let [username (some-> username (string/trim))] + (every? false? + [(string/blank? username) + (homoglyph/matches username constants/console-chat-id) + (homoglyph/matches username constants/wallet-chat-id) + (string/includes? username chat.constants/command-char) + (string/includes? username chat.constants/bot-char)]))) + +(defn correct-email? [email] + (let [pattern #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"] + (or (string/blank? email) + (and (string? email) (re-matches pattern email))))) + +(spec/def ::name correct-name?) +(spec/def ::email correct-email?) + +(spec/def ::profile (spec/keys :req-un [::name])) + +;; EDIT PROFILE +(spec/def :profile/profile-edit (spec/nilable map?)) diff --git a/src/status_im/ui/screens/profile/edit/views.cljs b/src/status_im/ui/screens/profile/edit/views.cljs new file mode 100644 index 0000000000..53e8bc08de --- /dev/null +++ b/src/status_im/ui/screens/profile/edit/views.cljs @@ -0,0 +1,106 @@ +(ns status-im.ui.screens.profile.edit.views + (:require [cljs.spec.alpha :as spec] + [clojure.string :as string] + [re-frame.core :refer [dispatch]] + [reagent.core :as reagent] + [status-im.components.camera :as camera] + [status-im.components.chat-icon.screen :refer [my-profile-icon]] + [status-im.components.context-menu :refer [context-menu]] + [status-im.components.react :as react] + [status-im.components.status-bar :refer [status-bar]] + [status-im.components.sticky-button :refer [sticky-button]] + [status-im.components.text-input-with-label.view :refer [text-input-with-label]] + [status-im.components.toolbar-new.view :refer [toolbar]] + [status-im.i18n :refer [label]] + [status-im.ui.screens.profile.db :as db] + [status-im.ui.screens.profile.events :as profile.events] + [status-im.ui.screens.profile.styles :as styles] + [status-im.ui.screens.profile.views :refer [colorize-status-hashtags]] + [status-im.utils.utils :as utils :refer [clean-text]]) + (:require-macros [status-im.utils.views :refer [defview letsubs]])) + +(defn edit-my-profile-toolbar [] + [toolbar {:title (label :t/edit-profile) + :actions [{:image :blank}]}]) + +(defview profile-name-input [] + (letsubs [new-profile-name [:get-in [:profile-edit :name]]] + [react/view + [text-input-with-label {:label (label :t/name) + :default-value new-profile-name + :on-change-text #(dispatch [:set-in [:profile-edit :name] %])}]])) + +(def profile-icon-options + [{:text (label :t/image-source-gallery) + :value #(dispatch [:open-image-picker])} + {:text (label :t/image-source-make-photo) + :value (fn [] + (dispatch [:request-permissions + [:camera :write-external-storage] + (fn [] + (camera/request-access + #(if % (dispatch [:navigate-to :profile-photo-capture]) + (utils/show-popup (label :t/error) + (label :t/camera-access-error)))))]))}]) + +(defn edit-profile-bage [contact] + [react/view styles/edit-profile-bage + [react/view styles/edit-profile-icon-container + [context-menu + [my-profile-icon {:account contact + :edit? true}] + profile-icon-options + styles/context-menu-custom-styles]] + [react/view styles/edit-profile-name-container + [profile-name-input]]]) + +(defn edit-profile-status [{:keys [status edit-status?]}] + (let [input-ref (reagent/atom nil)] + [react/view styles/edit-profile-status + [react/scroll-view + (if edit-status? + [react/text-input + {:ref #(reset! input-ref %) + :auto-focus edit-status? + :multiline true + :max-length 140 + :placeholder (label :t/status) + :style styles/profile-status-input + :on-change-text #(dispatch [:set-in [:profile-edit :status] (clean-text %)]) + :on-blur #(dispatch [:set-in [:profile-edit :edit-status?] false]) + :blur-on-submit true + :on-submit-editing #(.blur @input-ref) + :default-value status}] + [react/touchable-highlight {:on-press #(dispatch [:set-in [:profile-edit :edit-status?] true])} + [react/view + (if (string/blank? status) + [react/text {:style styles/add-a-status} + (label :t/status)] + [react/text {:style styles/profile-status-text} + (colorize-status-hashtags status)])]])]])) + +(defn status-prompt [{:keys [status]}] + (when (or (nil? status) (string/blank? status)) + [react/view styles/status-prompt + [react/text {:style styles/status-prompt-text} + (colorize-status-hashtags (label :t/status-prompt))]])) + +(defview edit-my-profile [] + (letsubs [current-account [:get-current-account] + changed-account [:get :profile-edit]] + {:component-will-unmount #(dispatch [:set-in [:profile-edit :edit-status?] false])} + (let [profile-edit-data-valid? (spec/valid? ::db/profile changed-account) + profile-edit-data-changed? (or (not= (:name current-account) (:name changed-account)) + (not= (:status current-account) (:status changed-account)) + (not= (:photo-path current-account) (:photo-path changed-account)))] + [react/keyboard-avoiding-view {:style styles/profile} + [status-bar] + [edit-my-profile-toolbar] + [react/view styles/edit-my-profile-form + [edit-profile-bage changed-account] + [edit-profile-status changed-account] + [status-prompt changed-account]] + (when (and profile-edit-data-changed? profile-edit-data-valid?) + [sticky-button (label :t/save) #(do + (dispatch [:check-status-change (:status changed-account)]) + (dispatch [:account-update changed-account]))])]))) diff --git a/src/status_im/ui/screens/profile/events.cljs b/src/status_im/ui/screens/profile/events.cljs new file mode 100644 index 0000000000..0fdd621390 --- /dev/null +++ b/src/status_im/ui/screens/profile/events.cljs @@ -0,0 +1,58 @@ +(ns status-im.ui.screens.profile.events + (:require [re-frame.core :as re-frame :refer [reg-fx]] + [status-im.components.react :refer [show-image-picker]] + [status-im.constants :refer [console-chat-id]] + [status-im.ui.screens.profile.navigation] + [status-im.utils.handlers :as handlers] + [status-im.utils.image-processing :refer [img->base64]] + [taoensso.timbre :as log])) + +(defn message-user [identity] + (when identity + (re-frame/dispatch [:navigation-replace :chat identity]))) + +(handlers/register-handler + :open-image-picker + (handlers/side-effect! + (fn [_ _] + (show-image-picker + (fn [image] + (let [path (get (js->clj image) "path") + _ (log/debug path) + on-success (fn [base64] + (re-frame/dispatch [:set-in [:profile-edit :photo-path] (str "data:image/jpeg;base64," base64)])) + on-error (fn [type error] + (.log js/console type error))] + (img->base64 path on-success on-error))))))) + +(handlers/register-handler + :phone-number-change-requested + ;; Switch user to the console issuing the !phone command automatically to let him change his phone number. + ;; We allow to change phone number only from console because this requires entering SMS verification code. + (handlers/side-effect! + (fn [db _] + (re-frame/dispatch [:navigate-to :chat console-chat-id]) + (js/setTimeout #(re-frame/dispatch [:select-chat-input-command {:name "phone"}]) 500)))) + +(handlers/register-handler + :open-chat-with-the-send-transaction + (handlers/side-effect! + (fn [db [_ chat-id]] + (re-frame/dispatch [:clear-seq-arguments]) + (re-frame/dispatch [:navigate-to :chat chat-id]) + (js/setTimeout #(re-frame/dispatch [:select-chat-input-command {:name "send"}]) 500)))) + +(defn prepare-edit-profile + [{:keys [current-account-id] :as db} _] + (let [current-account (select-keys (get-in db [:accounts current-account-id]) + [:name :photo-path :status])] + (update-in db [:profile-edit] merge current-account))) + +(defn open-edit-profile [_ _] + (re-frame/dispatch [:navigate-to :edit-my-profile])) + +(handlers/register-handler + :open-edit-my-profile + (handlers/handlers-> + prepare-edit-profile + open-edit-profile)) diff --git a/src/status_im/ui/screens/profile/navigation.cljs b/src/status_im/ui/screens/profile/navigation.cljs new file mode 100644 index 0000000000..02650c4d10 --- /dev/null +++ b/src/status_im/ui/screens/profile/navigation.cljs @@ -0,0 +1,9 @@ +(ns status-im.ui.screens.profile.navigation + (:require [status-im.ui.screens.navigation :as navigation])) + +(defmethod navigation/preload-data! :qr-code-view + [{:keys [current-account-id] :as db} [_ _ {:keys [contact qr-source amount?]}]] + (assoc db :qr-modal {:contact (or contact + (get-in db [:accounts current-account-id])) + :qr-source qr-source + :amount? amount?})) diff --git a/src/status_im/ui/screens/profile/photo_capture/styles.cljs b/src/status_im/ui/screens/profile/photo_capture/styles.cljs new file mode 100644 index 0000000000..3c12b5b94f --- /dev/null +++ b/src/status_im/ui/screens/profile/photo_capture/styles.cljs @@ -0,0 +1,5 @@ +(ns status-im.ui.screens.profile.photo-capture.styles) + +(def container + {:flex 1 + :background-color :white}) diff --git a/src/status_im/profile/photo_capture/screen.cljs b/src/status_im/ui/screens/profile/photo_capture/views.cljs similarity index 50% rename from src/status_im/profile/photo_capture/screen.cljs rename to src/status_im/ui/screens/profile/photo_capture/views.cljs index 14ef9207f6..797d195064 100644 --- a/src/status_im/profile/photo_capture/screen.cljs +++ b/src/status_im/ui/screens/profile/photo_capture/views.cljs @@ -1,28 +1,21 @@ -(ns status-im.profile.photo-capture.screen - (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] - [clojure.walk :refer [keywordize-keys]] - [status-im.components.react :refer [view - text - image - touchable-highlight]] - [status-im.components.camera :refer [camera - aspects - capture-targets]] - [status-im.components.styles :refer [icon-back]] - [status-im.components.icons.custom-icons :refer [ion-icon]] - [status-im.components.status-bar :refer [status-bar]] - [status-im.components.toolbar.view :refer [toolbar]] - [status-im.components.toolbar.actions :as act] - [status-im.components.toolbar.styles :refer [toolbar-background1]] - [status-im.utils.image-processing :refer [img->base64]] - [status-im.profile.photo-capture.styles :as st] - [status-im.i18n :refer [label]] +(ns status-im.ui.screens.profile.photo-capture.views + (:require [re-frame.core :refer [dispatch]] [reagent.core :as r] + [status-im.components.camera :refer [aspects camera capture-targets]] + [status-im.components.icons.custom-icons :refer [ion-icon]] + [status-im.components.react :as react] + [status-im.components.status-bar :refer [status-bar]] + [status-im.components.toolbar.actions :as actions] + [status-im.components.toolbar.styles :refer [toolbar-background1]] + [status-im.components.toolbar.view :refer [toolbar]] + [status-im.i18n :refer [label]] + [status-im.ui.screens.profile.photo-capture.styles :as styles] + [status-im.utils.image-processing :refer [img->base64]] [taoensso.timbre :as log])) (defn image-captured [data] (let [path (.-path data) - _ (log/debug "Captured image: " path) + _ (log/debug "Captured image: " path) on-success (fn [base64] (log/debug "Captured success: " base64) (dispatch [:set-in [:profile-edit :photo-path] (str "data:image/jpeg;base64," base64)]) @@ -33,10 +26,10 @@ (defn profile-photo-capture [] (let [camera-ref (r/atom nil)] - [view st/container + [react/view styles/container [status-bar] [toolbar {:title (label :t/image-source-title) - :nav-action (act/back #(dispatch [:navigate-back])) + :nav-action (actions/back #(dispatch [:navigate-back])) :background-color toolbar-background1}] [camera {:style {:flex 1} :aspect (:fill aspects) @@ -44,14 +37,14 @@ :captureTarget (:disk capture-targets) :type "front" :ref #(reset! camera-ref %)}] - [view {:style {:padding 10 - :background-color toolbar-background1}} - [touchable-highlight {:style {:align-self "center"} - :on-press (fn [] - (let [camera @camera-ref] - (-> (.capture camera) - (.then image-captured) - (.catch #(log/debug "Error capturing image: " %)))))} - [view + [react/view {:style {:padding 10 + :background-color toolbar-background1}} + [react/touchable-highlight {:style {:align-self "center"} + :on-press (fn [] + (let [camera @camera-ref] + (-> (.capture camera) + (.then image-captured) + (.catch #(log/debug "Error capturing image: " %)))))} + [react/view [ion-icon {:name :md-camera :style {:font-size 36}}]]]]])) diff --git a/src/status_im/profile/qr_code/styles.cljs b/src/status_im/ui/screens/profile/qr_code/styles.cljs similarity index 93% rename from src/status_im/profile/qr_code/styles.cljs rename to src/status_im/ui/screens/profile/qr_code/styles.cljs index 4292dd0ed6..20b4bb159d 100644 --- a/src/status_im/profile/qr_code/styles.cljs +++ b/src/status_im/ui/screens/profile/qr_code/styles.cljs @@ -1,6 +1,5 @@ -(ns status-im.profile.qr-code.styles - (:require [status-im.components.styles :refer [color-white]] - [status-im.components.react :as r])) +(ns status-im.ui.screens.profile.qr-code.styles + (:require [status-im.components.styles :refer [color-white]])) (def photo-container {:flex 0.2 diff --git a/src/status_im/ui/screens/profile/qr_code/views.cljs b/src/status_im/ui/screens/profile/qr_code/views.cljs new file mode 100644 index 0000000000..197f344440 --- /dev/null +++ b/src/status_im/ui/screens/profile/qr_code/views.cljs @@ -0,0 +1,50 @@ +(ns status-im.ui.screens.profile.qr-code.views + (:require [clojure.string :as string] + [re-frame.core :refer [dispatch]] + [status-im.components.qr-code :refer [qr-code]] + [status-im.components.react :as react] + [status-im.components.status-bar :refer [status-bar]] + [status-im.i18n :refer [label]] + [status-im.ui.screens.profile.qr-code.styles :as styles]) + (:require-macros [status-im.utils.views :refer [defview letsubs]])) + +(defview qr-code-view [] + (letsubs [{:keys [photo-path address name] :as contact} [:get-in [:qr-modal :contact]] + {:keys [qr-source amount? dimensions]} [:get :qr-modal] + {:keys [amount]} [:get :contacts/click-params]] + [react/view styles/wallet-qr-code + [status-bar {:type :modal}] + [react/view styles/account-toolbar + [react/view styles/wallet-account-container + [react/view styles/qr-photo-container + [react/view styles/account-photo-container + [react/image {:source {:uri (if (string/blank? photo-path) :avatar photo-path)} + :style styles/photo-image}]]] + [react/view styles/name-container + [react/text {:style styles/name-text + :number-of-lines 1} name]] + [react/view styles/online-container + [react/touchable-highlight {:onPress #(dispatch [:navigate-back])} + [react/view styles/online-image-container + [react/icon :close_white]]]]]] + [react/view {:style styles/qr-code + :on-layout #(let [layout (.. % -nativeEvent -layout)] + (dispatch [:set-in [:qr-modal :dimensions] {:width (.-width layout) + :height (.-height layout)}]))} + (when (:width dimensions) + [react/view {:style (styles/qr-code-container dimensions)} + [qr-code {:value (if amount? + (prn-str {:address (get contact qr-source) + :amount amount}) + (str "ethereum:" (get contact qr-source))) + :size (- (min (:width dimensions) + (:height dimensions)) + 80)}]])] + [react/view styles/footer + [react/view styles/wallet-info + [react/text {:style styles/wallet-name-text} (label :t/main-wallet)] + [react/text {:style styles/wallet-address-text} address]] + + [react/touchable-highlight {:onPress #(dispatch [:navigate-back])} + [react/view styles/done-button + [react/text {:style styles/done-button-text} (label :t/done)]]]]])) diff --git a/src/status_im/profile/styles.cljs b/src/status_im/ui/screens/profile/styles.cljs similarity index 85% rename from src/status_im/profile/styles.cljs rename to src/status_im/ui/screens/profile/styles.cljs index aa70301a7a..ff46af46ab 100644 --- a/src/status_im/profile/styles.cljs +++ b/src/status_im/ui/screens/profile/styles.cljs @@ -1,14 +1,15 @@ -(ns status-im.profile.styles - (:require-macros [status-im.utils.styles :refer [defstyle]]) - (:require [status-im.components.styles :refer [color-white - color-black - color-gray4 - color-gray5 - color-light-gray - color-light-blue - color-light-blue-transparent - text1-color]] - [status-im.utils.platform :as p])) +(ns status-im.ui.screens.profile.styles + (:require [status-im.components.styles + :refer + [color-black + color-gray4 + color-gray5 + color-light-blue + color-light-gray + color-white + text1-color]] + [status-im.utils.platform :as platform]) + (:require-macros [status-im.utils.styles :refer [defstyle]])) (def profile {:flex 1 @@ -129,7 +130,7 @@ {:margin-left 16}) (def edit-line-color - (if p/ios? + (if platform/ios? (str color-gray5 "80") color-gray5)) @@ -137,7 +138,7 @@ color-light-blue) (def profile-focus-line-height - (get-in p/platform-specific [:component-styles :text-field-focus-line-height])) + (get-in platform/platform-specific [:component-styles :text-field-focus-line-height])) (defstyle profile-name-input {:color text1-color @@ -189,4 +190,3 @@ (def add-a-status (merge profile-status-text {:color color-gray4})) - diff --git a/src/status_im/ui/screens/profile/views.cljs b/src/status_im/ui/screens/profile/views.cljs new file mode 100644 index 0000000000..4e5f12a138 --- /dev/null +++ b/src/status_im/ui/screens/profile/views.cljs @@ -0,0 +1,221 @@ +(ns status-im.ui.screens.profile.views + (:require [clojure.string :as string] + [re-frame.core :refer [dispatch]] + [status-im.components.action-button.action-button + :refer + [action-button action-button-disabled action-separator]] + [status-im.components.action-button.styles :refer [actions-list]] + [status-im.components.chat-icon.screen :refer [my-profile-icon]] + [status-im.components.common.common + :refer + [bottom-shadow form-spacer separator]] + [status-im.components.context-menu :refer [context-menu]] + [status-im.components.list-selection :refer [share-options]] + [status-im.components.react :as react] + [status-im.components.status-bar :refer [status-bar]] + [status-im.components.styles :refer [color-blue]] + [status-im.components.toolbar-new.actions :as actions] + [status-im.components.toolbar-new.view :refer [toolbar]] + [status-im.i18n :refer [label]] + [status-im.ui.screens.profile.events :as profile.event :refer [message-user]] + [status-im.ui.screens.profile.styles :as styles] + [status-im.utils.datetime :as time] + [status-im.utils.utils :refer [hash-tag?]]) + (:require-macros [status-im.utils.views :refer [defview]])) + +(defn my-profile-toolbar [] + [toolbar {:actions [(actions/opts [{:value #(dispatch [:open-edit-my-profile]) + :text (label :t/edit)}])]}]) + +(defn profile-toolbar [contact] + [toolbar + (when (and (not (:pending? contact)) + (not (:unremovable? contact))) + {:actions [(actions/opts [{:value #(dispatch [:hide-contact contact]) + :text (label :t/remove-from-contacts)}])]})]) + +(defn online-text [last-online] + (let [last-online-date (time/to-date last-online) + now-date (time/now)] + (if (and (pos? last-online) + (<= last-online-date now-date)) + (time/time-ago last-online-date) + (label :t/active-unknown)))) + +(defn profile-badge [{:keys [name last-online] :as contact}] + [react/view styles/profile-bage + [my-profile-icon {:account contact + :edit? false}] + [react/view styles/profile-badge-name-container + [react/text {:style styles/profile-name-text + :number-of-lines 1} + name] + (when-not (nil? last-online) + [react/view styles/profile-activity-status-container + [react/text {:style styles/profile-activity-status-text} + (online-text last-online)]])]]) + +(defn profile-actions [{:keys [pending? whisper-identity dapp?]} chat-id] + [react/view actions-list + (if pending? + [action-button (label :t/add-to-contacts) + :add_blue + #(dispatch [:add-pending-contact chat-id])] + [action-button-disabled (label :t/in-contacts) + :ok_dark]) + [action-separator] + [action-button (label :t/start-conversation) + :chats_blue + #(message-user whisper-identity)] + (when-not dapp? + [react/view + [action-separator] + [action-button (label :t/send-transaction) + :arrow_right_blue + #(dispatch [:open-chat-with-the-send-transaction chat-id])]])]) + +(defn profile-info-item [{:keys [label value options text-mode empty-value?]}] + [react/view styles/profile-setting-item + [react/view (styles/profile-info-text-container options) + [react/text {:style styles/profile-setting-title} + label] + [react/view styles/profile-setting-spacing] + [react/text {:style (if empty-value? + styles/profile-setting-text-empty + styles/profile-setting-text) + :number-of-lines 1 + :ellipsizeMode text-mode} + value]] + (when options + [context-menu + [react/icon :options_gray] + options + nil + styles/profile-info-item-button])]) + +(defn show-qr [contact qr-source] + #(dispatch [:navigate-to-modal :qr-code-view {:contact contact + :qr-source qr-source}])) + +(defn profile-options [contact k text] + (into [] + (concat [{:value (show-qr contact k) + :text (label :t/show-qr)}] + (when text + (share-options text))))) + +(defn profile-info-address-item [{:keys [address] :as contact}] + [profile-info-item + {:label (label :t/address) + :value address + :options (profile-options contact :address address) + :text-mode :middle}]) + +(defn profile-info-public-key-item [public-key contact] + [profile-info-item + {:label (label :t/public-key) + :value public-key + :options (profile-options contact :public-key public-key) + :text-mode :middle}]) + +(defn info-item-separator [] + [separator styles/info-item-separator]) + +(defn tag-view [tag] + [react/text {:style {:color color-blue} + :font :medium} + (str tag " ")]) + +(defn colorize-status-hashtags [status] + (for [[i status] (map-indexed vector (string/split status #" "))] + (if (hash-tag? status) + ^{:key (str "item-" i)} + [tag-view status] + ^{:key (str "item-" i)} + (str status " ")))) + +(defn profile-info-phone-item [phone & [options]] + (let [phone-empty? (or (nil? phone) (string/blank? phone)) + phone-text (if phone-empty? + (label :t/not-specified) + phone)] + [profile-info-item {:label (label :t/phone-number) + :value phone-text + :options options + :empty-value? phone-empty?}])) + +(defn profile-info [{:keys [whisper-identity status phone] :as contact}] + [react/view + [profile-info-address-item contact] + [info-item-separator] + [profile-info-public-key-item whisper-identity contact] + [info-item-separator] + [profile-info-phone-item phone]]) + +(defn my-profile-info [{:keys [public-key status phone] :as contact}] + [react/view + [profile-info-address-item contact] + [info-item-separator] + [profile-info-public-key-item public-key contact] + [info-item-separator] + [profile-info-phone-item + phone + [{:value #(dispatch [:phone-number-change-requested]) + :text (label :t/edit)}]]]) + +(defn- profile-status-on-press [] + (dispatch [:set-in [:profile-edit :edit-status?] true]) + (dispatch [:open-edit-my-profile])) + +(defn profile-status [status & [edit?]] + [react/view styles/profile-status-container + (if (or (nil? status) (string/blank? status)) + [react/touchable-highlight {:on-press profile-status-on-press} + [react/view + [react/text {:style styles/add-a-status} + (label :t/add-a-status)]]] + [react/scroll-view + [react/touchable-highlight {:on-press (when edit? profile-status-on-press)} + [react/view + [react/text {:style styles/profile-status-text} + (colorize-status-hashtags status)]]]])]) + +(defview my-profile [] + (letsubs [{:keys [status] :as current-account} [:get-current-account]] + [react/view styles/profile + [status-bar] + [my-profile-toolbar] + [react/scroll-view + [react/view styles/profile-form + [profile-badge current-account] + [profile-status status true]] + [form-spacer] + [react/view actions-list + [action-button (label :t/show-qr) + :q_r_blue + (show-qr current-account :public-key)]] + [form-spacer] + [react/view styles/profile-info-container + [my-profile-info current-account] + [bottom-shadow]]]])) + +(defview profile [] + (letsubs [{:keys [pending? + status + whisper-identity] + :as contact} [:contact] + chat-id [:get :current-chat-id]] + [react/view styles/profile + [status-bar] + [profile-toolbar contact] + [react/scroll-view + [react/view styles/profile-form + [profile-badge contact] + (when (and (not (nil? status)) (not (string/blank? status))) + [profile-status status])] + [form-spacer] + [profile-actions contact chat-id] + [form-spacer] + [react/view styles/profile-info-container + [profile-info contact] + [bottom-shadow]]]])) diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index f232ff81e0..372c2b2aeb 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -37,10 +37,10 @@ add-participants-toggle-list]] [status-im.ui.screens.group.reorder.views :refer [reorder-groups]] - [status-im.profile.screen :refer [profile my-profile]] - [status-im.profile.edit.screen :refer [edit-my-profile]] - [status-im.profile.photo-capture.screen :refer [profile-photo-capture]] - [status-im.profile.qr-code.screen :refer [qr-code-view]] + [status-im.ui.screens.profile.views :refer [profile my-profile]] + [status-im.ui.screens.profile.edit.views :refer [edit-my-profile]] + [status-im.ui.screens.profile.photo-capture.views :refer [profile-photo-capture]] + [status-im.ui.screens.profile.qr-code.views :refer [qr-code-view]] [status-im.ui.screens.wallet.send.views :refer [send-transaction]])) ;;[status-im.ui.screens.wallet.receive.views :refer [receive-transaction]] @@ -103,4 +103,4 @@ :transaction-details transaction-details :confirmation-success confirmation-success :contact-list-modal contact-list-modal)] - [component])]])]]))))) \ No newline at end of file + [component])]])]]))))) diff --git a/src/status_im/utils/handlers.cljs b/src/status_im/utils/handlers.cljs index 73cbfec6a3..22d861a0c3 100644 --- a/src/status_im/utils/handlers.cljs +++ b/src/status_im/utils/handlers.cljs @@ -1,9 +1,9 @@ (ns status-im.utils.handlers - (:require [re-frame.core :refer [reg-event-db reg-event-fx]] + (:require [cljs.spec.alpha :as spec] + [clojure.string :as string] + [re-frame.core :refer [reg-event-db reg-event-fx]] [re-frame.interceptor :refer [->interceptor get-coeffect get-effect]] - [clojure.string :as str] - [taoensso.timbre :as log] - [cljs.spec.alpha :as s]) + [taoensso.timbre :as log]) (:require-macros status-im.utils.handlers)) (defn side-effect! @@ -16,24 +16,24 @@ (def debug-handlers-names "Interceptor which logs debug information to js/console for each event." (->interceptor - :id :debug-handlers-names - :before (fn debug-handlers-names-before - [context] - (log/debug "Handling re-frame event: " (first (get-coeffect context :event))) - context))) + :id :debug-handlers-names + :before (fn debug-handlers-names-before + [context] + (log/debug "Handling re-frame event: " (first (get-coeffect context :event))) + context))) (def check-spec "throw an exception if db doesn't match the spec" (->interceptor - :id check-spec - :after - (fn check-handler - [context] - (let [new-db (get-effect context :db) - v (get-coeffect context :event)] - (when (and new-db (not (s/valid? :status-im.ui.screens.db/db new-db))) - (throw (ex-info (str "spec check failed on: " (first v) "\n " (s/explain-str :status-im.ui.screens.db/db new-db)) {}))) - context)))) + :id check-spec + :after + (fn check-handler + [context] + (let [new-db (get-effect context :db) + v (get-coeffect context :event)] + (when (and new-db (not (spec/valid? :status-im.ui.screens.db/db new-db))) + (throw (ex-info (str "spec check failed on: " (first v) "\n " (spec/explain-str :status-im.ui.screens.db/db new-db)) {}))) + context)))) (defn register-handler ([name handler] (register-handler name nil handler)) @@ -52,7 +52,7 @@ (defn get-hashtags [status] (if status - (let [hashtags (map #(str/lower-case (subs % 1)) + (let [hashtags (map #(string/lower-case (subs % 1)) (re-seq #"#[^ !?,;:.]+" status))] (set (or hashtags []))) #{}))