Merge pull request #337 from status-im/bug/profile-screen-#298

Bug/profile screen #298
This commit is contained in:
Roman Volosovskyi 2016-10-14 15:13:50 +03:00 committed by GitHub
commit fe97c4c7fb
17 changed files with 3526 additions and 154 deletions

View File

@ -14,8 +14,9 @@
status-im.accounts.recover.handlers
[clojure.string :as str]
[status-im.utils.datetime :as time]
[status-im.utils.handlers :as u]
[status-im.utils.handlers :as u :refer [get-hashtags]]
[status-im.accounts.statuses :as statuses]
[status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.constants :refer [console-chat-id]]))
@ -36,7 +37,7 @@
{:keys [public private]} (protocol/new-keypair!)
account {:public-key public-key
:address address
:name address
:name (generate-gfy)
:status (rand-nth statuses/data)
:signed-up? true
:updates-public-key public
@ -74,6 +75,18 @@
:status status
:profile-image photo-path}}}})))
(register-handler
:check-status-change
(u/side-effect!
(fn [{:keys [current-account-id accounts]} [_ status]]
(let [{old-status :status :as account} (get accounts current-account-id)
status-updated? (and (not= status nil)
(not= status old-status))]
(when status-updated?
(let [hashtags (get-hashtags status)]
(when-not (empty? hashtags)
(dispatch [:broadcast-status status hashtags]))))))))
(register-handler
:account-update
(-> (fn [{:keys [current-account-id accounts] :as db} [_ data]]

View File

@ -6,6 +6,7 @@
[taoensso.timbre :as log]
[clojure.string :as str]
[status-im.utils.handlers :as u]
[status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.protocol.core :as protocol]))
(defn account-recovered [result]
@ -16,7 +17,7 @@
{:keys [public private]} (protocol/new-keypair!)
account {:public-key public-key
:address address
:name address
:name (generate-gfy)
:photo-path (identicon public-key)
:updates-public-key public
:updates-private-key private

View File

@ -14,7 +14,6 @@
[status-im.components.text-field.view :refer [text-field]]
[status-im.components.drawer.styles :as st]
[status-im.profile.validations :as v]
[status-im.profile.handlers :refer [update-profile]]
[status-im.resources :as res]
[status-im.i18n :refer [label]]
[status-im.components.react :refer [dismiss-keyboard!]]))
@ -43,8 +42,8 @@
name]])
(defview drawer-menu []
[{:keys [name address photo-path status] :as account} [:get-current-account]
{new-name :name :as profile-edit-data} [:get :profile-edit]
[{:keys [name photo-path status]} [:get-current-account]
{new-name :name new-status :status} [:get :profile-edit]
keyboard-height [:get :keyboard-height]]
[view st/drawer-menu
[touchable-without-feedback {:on-press #(dismiss-keyboard!)}
@ -60,10 +59,9 @@
:editable true
:input-style (st/name-input-text (s/valid? ::v/name (or new-name name)))
:wrapper-style st/name-input-wrapper
:value (if (not= name address)
name)
:value name
:on-change-text #(dispatch [:set-in [:profile-edit :name] %])
:on-end-editing #(update-profile account profile-edit-data)}]]
:on-end-editing #(dispatch [:account-update {:name new-name}])}]]
[view st/status-container
[text-input {:style st/status-input
:editable true
@ -73,7 +71,9 @@
:accessibility-label :input
:placeholder (label :t/profile-no-status)
:on-change-text #(dispatch [:set-in [:profile-edit :status] %])
:on-blur #(update-profile account profile-edit-data)
:on-blur (fn[]
(dispatch [:check-status-change new-status])
(dispatch [:account-update {:status new-status}]))
:default-value status}]]
[view st/menu-items-container
[menu-item {:name (label :t/profile)

View File

@ -0,0 +1,36 @@
(ns status-im.components.selectable-field.styles
(:require [status-im.utils.platform :refer [platform-specific]]))
(def selectable-field-container
{})
(def label-container
{:margin-bottom 13})
(def label
{:color "#838c93"
:background-color :transparent
:font-size 14})
(def text-container
{:padding 0
:margin-bottom 18
:margin 0})
(def text
{:font-size 16
:color "#555555"
:margin-right 16
:text-align-vertical :top})
(defn sized-text
[height]
(merge text {:height height
:margin-bottom 0
:margin-top 0
:padding-top 0
:padding-left 0
:margin-left 0
:padding-bottom 0}))

View File

@ -0,0 +1,61 @@
(ns status-im.components.selectable-field.view
(:require [status-im.components.react :refer [view
text-input
text]]
[reagent.core :as r]
[status-im.components.selectable-field.styles :as st]
[status-im.i18n :refer [label]]
[taoensso.timbre :as log]))
(defn- on-press
[event component]
(log/debug "Pressed " event component)
(r/set-state component {:focused? true}))
(defn- on-selection-change
[event component]
(let [selection (.-selection (.-nativeEvent event))
start (.-start selection)
end (.-end selection)]
(log/debug "Selection changed: " start end)))
(defn- on-layout-text
[event component]
(let [height (.-height (.-layout (.-nativeEvent event)))
{:keys [full-height]} (r/state component)]
(when (and (pos? height) (not full-height))
(r/set-state component {:full-height height
:measured? true}))))
(defn- reagent-render
[{:keys [label value props] :as data}]
(let [component (r/current-component)
{:keys [focused? measured? full-height]} (r/state component)]
(log/debug "reagent-render: " data focused? measured? full-height)
[view st/selectable-field-container
[view st/label-container
[text {:style st/label
:font :medium} (or label "")]]
[view st/text-container
(if focused?
[text-input {:style (st/sized-text full-height)
:multiline true
:selectTextOnFocus true
:editable true
:auto-focus true
:on-selection-change #(on-selection-change % component)
:on-focus #(log/debug "Focused" %)
:on-blur #(r/set-state component {:focused? false})
:value value}]
[text (merge {:style st/text
:on-press #(on-press % component)
:onLayout #(on-layout-text % component)
:font :default
:ellipsizeMode :middle
:number-of-lines (if measured? 1 0)} (or props {})) (or value "")])]]))
(defn selectable-field [{:keys [label value props]}]
(let [component-data {:display-name "selectable-field"
:reagent-render reagent-render}]
(reagent.core/create-class component-data)))

View File

@ -24,6 +24,7 @@
[cljs.spec :as s]
[status-im.contacts.validations :as v]
[status-im.contacts.styles :as st]
[status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.utils.hex :refer [normalize-hex]]))
@ -38,13 +39,13 @@
(fn [{:keys [contacts]}]
(if (> (count contacts) 0)
(let [{:keys [whisper-identity]} (first contacts)
contact {:name ""
contact {:name (generate-gfy)
:address id
:photo-path (identicon whisper-identity)
:whisper-identity whisper-identity}]
(dispatch [:add-new-contact contact]))
(dispatch [:set :new-contact-address-error (label :t/unknown-address)]))))
(dispatch [:add-new-contact {:name ""
(dispatch [:add-new-contact {:name (generate-gfy)
:photo-path (identicon id)
:whisper-identity id}])))

View File

@ -78,12 +78,6 @@
(register-handler :show-profile show-profile)
(defn show-profile-photo-capture
[db _]
(push-view db :profile-photo-capture))
(register-handler :show-profile-photo-capture show-profile-photo-capture)
(defn navigate-to-clean
[db [_ view-id]]
(-> db

View File

@ -5,45 +5,22 @@
[status-im.utils.image-processing :refer [img->base64]]
[status-im.i18n :refer [label]]
[status-im.utils.handlers :as u :refer [get-hashtags]]
[status-im.utils.platform :refer [ios?]]
[clojure.string :as str]
[status-im.profile.validations :as v]
[cljs.spec :as s]))
[cljs.spec :as s]
[taoensso.timbre :as log]))
(defn message-user [identity]
(when identity
(dispatch [:navigate-to :chat identity])))
(defn update-profile [{name :name
email :email
photo-path :photo-path
status :status}
{new-name :name
new-email :email
new-status :status
new-photo-path :photo-path}]
(let [new-name (if (or (not new-name)
(not (s/valid? ::v/name new-name)))
name
new-name)
status-updated? (and (not= new-status nil)
(not= status new-status))]
(when status-updated?
(let [hashtags (get-hashtags new-status)]
(when-not (empty? hashtags)
(dispatch [:broadcast-status new-status hashtags]))))
(dispatch [:account-update {:name new-name
:email (or new-email email)
:status (or new-status status)
:photo-path (or new-photo-path photo-path)}])))
(register-handler :open-image-picker
(u/side-effect!
(fn [_ _]
(show-image-picker
(fn [image]
(let [path (get (js->clj image) "path")
path (if ios? path (subs path 12))
_ (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]
@ -57,7 +34,7 @@
:options [(label :t/image-source-make-photo) (label :t/image-source-gallery)]
:callback (fn [index]
(case index
0 (dispatch [:show-profile-photo-capture])
0 (dispatch [:navigate-to :profile-photo-capture])
1 (dispatch [:open-image-picker])
:default))
:cancel-text (label :t/image-source-cancel)}))))

View File

@ -16,15 +16,18 @@
[status-im.utils.image-processing :refer [img->base64]]
[status-im.profile.photo-capture.styles :as st]
[status-im.i18n :refer [label]]
[reagent.core :as r]))
[reagent.core :as r]
[taoensso.timbre :as log]))
(defn image-captured [path]
(let [path (subs path 5)
(defn image-captured [data]
(let [path (.-path data)
_ (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)])
(dispatch [:navigate-back]))
on-error (fn [type error]
(.log js/console type error))]
(log/debug type error))]
(img->base64 path on-success on-error)))
(defn profile-photo-capture []
@ -47,7 +50,8 @@
:on-press (fn []
(let [camera @camera-ref]
(-> (.capture camera)
(.then image-captured))))}
(.then image-captured)
(.catch #(log/debug "Error capturing image: " %)))))}
[view
[ion-icon {:name :md-camera
:style {:font-size 36}}]]]]]))

View File

@ -12,24 +12,25 @@
scroll-view
touchable-highlight
touchable-opacity
show-image-picker]]
show-image-picker
dismiss-keyboard!]]
[status-im.components.icons.custom-icons :refer [oct-icon]]
[status-im.components.chat-icon.screen :refer [my-profile-icon]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.text-field.view :refer [text-field]]
[status-im.components.selectable-field.view :refer [selectable-field]]
[status-im.components.qr-code :refer [qr-code]]
[status-im.utils.handlers :refer [get-hashtags]]
[status-im.utils.phone-number :refer [format-phone-number]]
[status-im.utils.image-processing :refer [img->base64]]
[status-im.utils.platform :refer [platform-specific]]
[status-im.profile.handlers :refer [message-user
update-profile]]
[status-im.profile.handlers :refer [message-user]]
[status-im.profile.validations :as v]
[status-im.profile.styles :as st]
[status-im.utils.random :refer [id]]
[status-im.i18n :refer [label]]))
(defn toolbar [{:keys [account profile-edit-data edit?]}]
(let [profile-edit-data-valid? (s/valid? ::v/profile profile-edit-data)]
(defn toolbar [{:keys [account edit?]}]
(let [profile-edit-data-valid? (s/valid? ::v/profile account)]
[view
[touchable-highlight {:style st/back-btn-touchable
:on-press (fn []
@ -41,20 +42,32 @@
:on-press (fn []
(if edit?
(when profile-edit-data-valid?
(update-profile account profile-edit-data)
(dismiss-keyboard!)
(dispatch [:check-status-change (:status account)])
(dispatch [:account-update account])
(dispatch [:set-in [:profile-edit :edit?] false]))
(dispatch [:set-in [:profile-edit :edit?] true])))}
(dispatch [:set :profile-edit (merge account {:edit? true})])))}
[view st/actions-btn-container
(if edit?
[oct-icon {:name :check
:style (st/ok-btn-icon profile-edit-data-valid?)}]
[icon :dots st/edit-btn-icon])]]]))
(defn status-image-view [{{address :address
username :name} :account
{new-name :name} :profile-edit-data
photo-path :photo-path
status :status
(defn- get-text
[word]
(let [props (merge {:key (id)}
(if (str/starts-with? word "#")
{:style st/hashtag}
{}))]
[text props (str word " ")]))
(defn- highlight-tags
[status]
(->>
(str/split status #" ")
(map get-text)))
(defn status-image-view [{{:keys [name status photo-path]} :account
edit? :edit?}]
[view st/status-block
[view st/user-photo-container
@ -64,27 +77,27 @@
(dispatch [:open-image-source-selector list-selection-fn])))}
[view
[my-profile-icon {:account {:photo-path photo-path
:name username}
:name name}
:edit? edit?}]]]
[my-profile-icon {:account {:photo-path photo-path
:name username}
:name name}
:edit? edit?}])]
[text-field
{:line-color :white
:focus-line-color :white
:placeholder (label :t/user-anonymous)
:editable edit?
:input-style (st/username-input edit? (s/valid? ::v/name (or new-name username)))
:input-style (st/username-input edit? (s/valid? ::v/name name))
:wrapper-style st/username-wrapper
:value (if (not= username address)
username)
:value name
:on-change-text #(dispatch [:set-in [:profile-edit :name] %])}]
(if edit?
[text-input {:style st/status-input
:maxLength 140
:editable edit?
:placeholder (label :t/profile-no-status)
:on-change-text #(dispatch [:set-in [:profile-edit :status] %])
:default-value status}]])
:default-value status}]
[text {:style st/status-text} (highlight-tags status)])])
(defview profile []
[{whisper-identity :whisper-identity
@ -149,69 +162,43 @@
)}
[view [text {:style st/report-user-text} (label :t/report-user)]]]]]])
(defview my-profile-render []
[{public-key :public-key
address :address
username :name
email :email
photo-path :photo-path
phone :phone
status :status
:as account} [:get-current-account]
{edit? :edit?
new-name :name
new-email :email
new-status :status
new-photo-path :photo-path
:as profile-edit-data} [:get :profile-edit]]
[scroll-view {:style st/profile}
(defview my-profile []
[edit? [:get-in [:profile-edit :edit?]]
current-account [:get-current-account]
changed-account [:get :profile-edit]]
(let [{:keys [phone
address
public-key] :as account} (if edit?
changed-account
current-account)]
[scroll-view {:style st/profile
:keyboardShouldPersistTaps true}
[status-bar]
[toolbar {:account account
:profile-edit-data profile-edit-data
:edit? edit?}]
[status-image-view {:account account
:profile-edit-data profile-edit-data
:photo-path (or new-photo-path photo-path)
:status (or new-status status)
:edit? edit?}]
[scroll-view st/profile-properties-container
[text-field
{:editable false
:input-style st/profile-input-text-non-editable
:wrapper-style st/profile-input-wrapper
[scroll-view (merge st/profile-properties-container {:keyboardShouldPersistTaps true})
[view st/profile-property
[selectable-field {:label (label :t/phone-number)
:value (if (and phone (not (str/blank? phone)))
(format-phone-number phone))
:label (label :t/phone-number)}]
(format-phone-number phone)
(label :t/not-specified))}]
[view st/underline-container]]
[text-field
{:error (if-not (s/valid? ::v/email new-email)
(label :t/error-incorrect-email))
:error-color "#7099e6"
:editable edit?
:input-style (if edit?
st/profile-input-text
st/profile-input-text-non-editable)
:wrapper-style st/profile-input-wrapper
:value (if (and email (not (str/blank? email)))
email)
:label (label :t/email)
:on-change-text #(dispatch [:set-in [:profile-edit :email] %])}]
[view st/profile-property
[selectable-field {:label (label :t/address)
:value address}]
[view st/underline-container]]
[view st/profile-property
[selectable-field {:label (label :t/public-key)
:value public-key}]]
[view st/underline-container]
[view st/qr-code-container
;; TODO: this public key should be replaced by address
[qr-code {:value (str "ethereum:" public-key)
:size 220}]]]])
(defview my-profile []
[{username :name
email :email} [:get-current-account]]
(r/create-class
{:component-will-mount
(fn []
(dispatch [:set :profile-edit {:edit? false
:name username
:email email}]))
:reagent-render
my-profile-render}))
:size 220}]]]]))

View File

@ -68,20 +68,28 @@
{:flex-direction "column"
:align-items "center"
:justifyContent "center"
:margin-left 100
:margin-right 100})
:margin-bottom 38
:margin-left 55
:margin-right 55})
(def status-input
{:align-self "stretch"
:margin-left 16
:margin-right 16
:height 40
:margin-top -4
:margin-top 0
:font-size 14
:line-height 20
:text-align :center
:color text2-color})
(def status-text
{:text-align :center
:margin-left 0
:margin-right 0
:margin-top 10
:color text2-color})
(def btns-container
{:margin-top 18
:flex-direction :row})
@ -114,11 +122,12 @@
:height 16})
(def profile-properties-container
{:margin-top 20
:margin-left 16
:align-items :stretch
{:align-items :stretch
:flex-firection :column})
(def profile-property
{:margin-left 16})
(def profile-input-wrapper
{:margin-bottom 16})
@ -144,3 +153,12 @@
{:flex 1
:alignItems :center
:margin 32})
(def hashtag
{:color "#7099e6"})
(def underline-container
{:background-color "#0000001f"
:margin-bottom 18
:height 1
:align-items :center})

View File

@ -18,4 +18,4 @@
(s/def ::name correct-name?)
(s/def ::email correct-email?)
(s/def ::profile (s/keys :req-un [::name ::email]))
(s/def ::profile (s/keys :req-un [::name]))

View File

@ -63,6 +63,7 @@
:username "Username"
:user-anonymous "Anonymous"
:not-specified "Not specified"
:public-key "Public Key"
:phone-number "Phone number"
:email "Email"
:profile-no-status "No status"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
(ns status-im.utils.gfycat.core
(:require [status-im.utils.gfycat.animals :as animals]
[status-im.utils.gfycat.adjectives :as adjectives]
[clojure.string :as str]))
(defn- pick-random
[vector]
(-> (rand-nth vector)
str/capitalize))
(defn generate-gfy
[]
(let [first-adjective (pick-random adjectives/data)
second-adjective (pick-random adjectives/data)
animal (pick-random animals/data)]
(str first-adjective " " second-adjective " " animal)))

View File

@ -1,6 +1,8 @@
(ns status-im.utils.image-processing
(:require [reagent.core :as r]
[status-im.utils.fs :refer [read-file]]))
[status-im.utils.fs :refer [read-file]]
[taoensso.timbre :as log]
[clojure.string :as str]))
(def resizer-class (js/require "react-native-image-resizer"))
@ -19,7 +21,10 @@
(defn img->base64 [path on-success on-error]
(let [on-resized (fn [path]
(image-base64-encode path on-success on-error))
(let [path (str/replace path "file:" "")]
(log/debug "Resized: " path)
(image-base64-encode path on-success on-error)))
on-error (fn [error]
(log/debug "Resized error: " error)
(on-error :resize error))]
(resize path 150 150 on-resized on-error)))