feat: implement edit profile and change name (#18270)
This commit is contained in:
parent
fb58d7205e
commit
b4d27d287a
|
@ -61,7 +61,7 @@
|
||||||
:container-style])
|
:container-style])
|
||||||
|
|
||||||
(defn- base-input
|
(defn- base-input
|
||||||
[{:keys [on-change-text on-char-limit-reach weight]}]
|
[{:keys [on-change-text on-char-limit-reach weight default-value]}]
|
||||||
(let [status (reagent/atom :default)
|
(let [status (reagent/atom :default)
|
||||||
internal-on-focus #(reset! status :focus)
|
internal-on-focus #(reset! status :focus)
|
||||||
internal-on-blur #(reset! status :default)
|
internal-on-blur #(reset! status :default)
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
(if (> height min-height)
|
(if (> height min-height)
|
||||||
(reset! multiple-lines? true)
|
(reset! multiple-lines? true)
|
||||||
(reset! multiple-lines? false)))
|
(reset! multiple-lines? false)))
|
||||||
char-count (reagent/atom 0)
|
char-count (reagent/atom (count default-value))
|
||||||
update-char-limit! (fn [new-text char-limit]
|
update-char-limit! (fn [new-text char-limit]
|
||||||
(when on-change-text (on-change-text new-text))
|
(when on-change-text (on-change-text new-text))
|
||||||
(let [amount-chars (count new-text)]
|
(let [amount-chars (count new-text)]
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
[react-native.core :as rn]))
|
[react-native.core :as rn]))
|
||||||
|
|
||||||
(defn view
|
(defn view
|
||||||
[{:keys [type]} & children]
|
[{:keys [type container-style]} & children]
|
||||||
[rn/view {:style (style/overlay-background type)}
|
[rn/view {:style (style/overlay-background type)}
|
||||||
(if (= type :shell)
|
(if (= type :shell)
|
||||||
[blur/view
|
[blur/view
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
:blur-type :transparent
|
:blur-type :transparent
|
||||||
:overlay-color :transparent
|
:overlay-color :transparent
|
||||||
:style style/container}
|
:style style/container}
|
||||||
[rn/view {:style style/blur-container}
|
[rn/view {:style (merge style/blur-container container-style)}
|
||||||
children]]
|
children]]
|
||||||
[rn/view {:style style/container}
|
[rn/view {:style (merge style/container container-style)}
|
||||||
children])])
|
children])])
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
[react-native.core :as rn]))
|
[react-native.core :as rn]))
|
||||||
|
|
||||||
(defn- category-internal
|
(defn- category-internal
|
||||||
[{:keys [label data] :as props}]
|
[{:keys [label data container-style] :as props}]
|
||||||
[rn/view {:style (style/container label)}
|
[rn/view {:style (merge (style/container label) container-style)}
|
||||||
(when label
|
(when label
|
||||||
[text/text
|
[text/text
|
||||||
{:weight :medium
|
{:weight :medium
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
(defn sub-container
|
(defn sub-container
|
||||||
[align-action]
|
[align-action]
|
||||||
{:flex-direction :row
|
{:flex-direction :row
|
||||||
|
:padding-right 0.5
|
||||||
:align-items (or align-action :center)})
|
:align-items (or align-action :center)})
|
||||||
|
|
||||||
(def left-container
|
(defn left-container
|
||||||
{:margin-horizontal 12
|
[image?]
|
||||||
|
{:margin-horizontal (if image? 12 0)
|
||||||
:flex 1
|
:flex 1
|
||||||
:height "100%"
|
:height "100%"
|
||||||
:justify-content :flex-start})
|
:justify-content :flex-start})
|
||||||
|
@ -57,6 +59,7 @@
|
||||||
{:width 15
|
{:width 15
|
||||||
:height 15
|
:height 15
|
||||||
:border-radius 12
|
:border-radius 12
|
||||||
|
:margin-right 4
|
||||||
:background-color background-color})
|
:background-color background-color})
|
||||||
|
|
||||||
(def status-tag-container
|
(def status-tag-container
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
:accessibility-label accessibility-label}
|
:accessibility-label accessibility-label}
|
||||||
[rn/view {:style (style/left-sub-container props)}
|
[rn/view {:style (style/left-sub-container props)}
|
||||||
[image-component props]
|
[image-component props]
|
||||||
[rn/view {:style style/left-container}
|
[rn/view {:style (style/left-container (:image props))}
|
||||||
[text/text
|
[text/text
|
||||||
{:weight :medium
|
{:weight :medium
|
||||||
:style {:color (when blur? colors/white)}} title]
|
:style {:color (when blur? colors/white)}} title]
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
(ns status-im.common.validation.profile
|
||||||
|
(:require [clojure.string :as string]
|
||||||
|
[utils.i18n :as i18n]))
|
||||||
|
|
||||||
|
;; NOTE - validation should match with Desktop
|
||||||
|
;; https://github.com/status-im/status-desktop/blob/2ba96803168461088346bf5030df750cb226df4c/ui/imports/utils/Constants.qml#L468
|
||||||
|
(def min-length 5)
|
||||||
|
(def max-length 24)
|
||||||
|
|
||||||
|
(def emoji-regex
|
||||||
|
#"(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])")
|
||||||
|
|
||||||
|
(def status-regex #"^[a-zA-Z0-9\-_ ]+$")
|
||||||
|
|
||||||
|
(def common-names ["Ethereum" "Bitcoin"])
|
||||||
|
|
||||||
|
(defn has-emojis? [s] (boolean (re-find emoji-regex s)))
|
||||||
|
|
||||||
|
(defn has-common-names? [s] (pos? (count (filter #(string/includes? s %) common-names))))
|
||||||
|
|
||||||
|
(defn has-special-characters? [s] (not (re-find status-regex s)))
|
||||||
|
|
||||||
|
(defn name-too-short? [s] (< (count (string/trim (str s))) min-length))
|
||||||
|
|
||||||
|
(defn name-too-long? [s] (> (count (string/trim (str s))) max-length))
|
||||||
|
|
||||||
|
(defn validation-name
|
||||||
|
[s]
|
||||||
|
(cond
|
||||||
|
(or (= s nil) (= s "")) nil
|
||||||
|
(string/ends-with? s "-eth") (i18n/label :t/ending-not-allowed {:ending "-eth"})
|
||||||
|
(string/ends-with? s "_eth") (i18n/label :t/ending-not-allowed {:ending "_eth"})
|
||||||
|
(string/ends-with? s ".eth") (i18n/label :t/ending-not-allowed {:ending ".eth"})
|
||||||
|
(string/starts-with? s " ") (i18n/label :t/start-with-space)
|
||||||
|
(string/ends-with? s " ") (i18n/label :t/ends-with-space)
|
||||||
|
(has-common-names? s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/common-names)})
|
||||||
|
(has-emojis? s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/emojis)})
|
||||||
|
(has-special-characters? s) (i18n/label :t/are-not-allowed
|
||||||
|
{:check (i18n/label :t/special-characters)})
|
||||||
|
(name-too-short? s) (i18n/label :t/minimum-characters {:min-chars min-length})
|
||||||
|
(name-too-long? s) (i18n/label :t/profile-name-is-too-long)))
|
|
@ -0,0 +1,52 @@
|
||||||
|
(ns status-im.common.validation.profile-test
|
||||||
|
(:require
|
||||||
|
[cljs.test :refer-macros [deftest are]]
|
||||||
|
[status-im.common.validation.profile :as profile-validator]
|
||||||
|
[utils.i18n :as i18n]))
|
||||||
|
|
||||||
|
(deftest has-emojis-test
|
||||||
|
(are [arg expected]
|
||||||
|
(expected (profile-validator/has-emojis? arg))
|
||||||
|
"Hello 😊" true?
|
||||||
|
"Hello" false?))
|
||||||
|
|
||||||
|
(deftest has-common-names-test
|
||||||
|
(are [arg expected]
|
||||||
|
(expected (profile-validator/has-common-names? arg))
|
||||||
|
"Ethereum" true?
|
||||||
|
"Hello" false?))
|
||||||
|
|
||||||
|
(deftest has-special-characters-test
|
||||||
|
(are [arg expected]
|
||||||
|
(expected (profile-validator/has-special-characters? arg))
|
||||||
|
"@name" true?
|
||||||
|
"name" false?))
|
||||||
|
|
||||||
|
(deftest name-too-short-test
|
||||||
|
(are [arg expected]
|
||||||
|
(expected (profile-validator/name-too-short? arg))
|
||||||
|
"abc" true?
|
||||||
|
"abcdef" false?))
|
||||||
|
|
||||||
|
(deftest name-too-long-test
|
||||||
|
(are [arg expected]
|
||||||
|
(expected (profile-validator/name-too-long? arg))
|
||||||
|
(apply str (repeat 25 "a")) true?
|
||||||
|
"abcdef" false?))
|
||||||
|
|
||||||
|
(deftest validation-name-test
|
||||||
|
(are [arg expected]
|
||||||
|
(= (profile-validator/validation-name arg) expected)
|
||||||
|
nil nil
|
||||||
|
"" nil
|
||||||
|
"@name" (i18n/label :t/are-not-allowed
|
||||||
|
{:check (i18n/label :t/special-characters)})
|
||||||
|
"name-eth" (i18n/label :t/ending-not-allowed {:ending "-eth"})
|
||||||
|
"name_eth" (i18n/label :t/ending-not-allowed {:ending "_eth"})
|
||||||
|
"name.eth" (i18n/label :t/ending-not-allowed {:ending ".eth"})
|
||||||
|
" name" (i18n/label :t/start-with-space)
|
||||||
|
"name " (i18n/label :t/ends-with-space)
|
||||||
|
"Ethereum" (i18n/label :t/are-not-allowed {:check (i18n/label :t/common-names)})
|
||||||
|
"Hello 😊" (i18n/label :t/are-not-allowed {:check (i18n/label :t/emojis)})
|
||||||
|
"abc" (i18n/label :t/minimum-characters {:min-chars 5})
|
||||||
|
(apply str (repeat 25 "a")) (i18n/label :t/profile-name-is-too-long)))
|
|
@ -10,6 +10,7 @@
|
||||||
[react-native.platform :as platform]
|
[react-native.platform :as platform]
|
||||||
[react-native.safe-area :as safe-area]
|
[react-native.safe-area :as safe-area]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
|
[status-im.common.validation.profile :as profile-validator]
|
||||||
[status-im.constants :as c]
|
[status-im.constants :as c]
|
||||||
[status-im.contexts.onboarding.create-profile.style :as style]
|
[status-im.contexts.onboarding.create-profile.style :as style]
|
||||||
[status-im.contexts.onboarding.select-photo.method-menu.view :as method-menu]
|
[status-im.contexts.onboarding.select-photo.method-menu.view :as method-menu]
|
||||||
|
@ -17,40 +18,9 @@
|
||||||
[utils.re-frame :as rf]
|
[utils.re-frame :as rf]
|
||||||
[utils.responsiveness :as responsiveness]))
|
[utils.responsiveness :as responsiveness]))
|
||||||
|
|
||||||
;; NOTE - validation should match with Desktop
|
|
||||||
;; https://github.com/status-im/status-desktop/blob/2ba96803168461088346bf5030df750cb226df4c/ui/imports/utils/Constants.qml#L468
|
|
||||||
;;
|
|
||||||
(def emoji-regex
|
|
||||||
(new
|
|
||||||
js/RegExp
|
|
||||||
#"(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])"
|
|
||||||
"i"))
|
|
||||||
(defn has-emojis [s] (re-find emoji-regex s))
|
|
||||||
(def common-names ["Ethereum" "Bitcoin"])
|
|
||||||
(defn has-common-names [s] (pos? (count (filter #(string/includes? s %) common-names))))
|
|
||||||
(def status-regex (new js/RegExp #"^[a-zA-Z0-9\-_ ]+$"))
|
|
||||||
(defn has-special-characters [s] (not (re-find status-regex s)))
|
|
||||||
(def min-length 5)
|
|
||||||
(defn length-not-valid [s] (< (count (string/trim (str s))) min-length))
|
|
||||||
(def scroll-view-height (reagent/atom 0))
|
(def scroll-view-height (reagent/atom 0))
|
||||||
(def content-container-height (reagent/atom 0))
|
(def content-container-height (reagent/atom 0))
|
||||||
|
|
||||||
(defn validation-message
|
|
||||||
[s]
|
|
||||||
(cond
|
|
||||||
(or (= s nil) (= s "")) nil
|
|
||||||
(has-special-characters s) (i18n/label :t/are-not-allowed
|
|
||||||
{:check (i18n/label :t/special-characters)})
|
|
||||||
(string/ends-with? s "-eth") (i18n/label :t/ending-not-allowed {:ending "-eth"})
|
|
||||||
(string/ends-with? s "_eth") (i18n/label :t/ending-not-allowed {:ending "_eth"})
|
|
||||||
(string/ends-with? s ".eth") (i18n/label :t/ending-not-allowed {:ending ".eth"})
|
|
||||||
(string/starts-with? s " ") (i18n/label :t/start-with-space)
|
|
||||||
(string/ends-with? s " ") (i18n/label :t/ends-with-space)
|
|
||||||
(has-common-names s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/common-names)})
|
|
||||||
(has-emojis s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/emojis)})
|
|
||||||
:else nil))
|
|
||||||
|
|
||||||
|
|
||||||
(defn show-button-background
|
(defn show-button-background
|
||||||
[keyboard-height keyboard-shown content-scroll-y]
|
[keyboard-height keyboard-shown content-scroll-y]
|
||||||
(let [button-container-height 64
|
(let [button-container-height 64
|
||||||
|
@ -66,7 +36,6 @@
|
||||||
:else
|
:else
|
||||||
false))))
|
false))))
|
||||||
|
|
||||||
|
|
||||||
(defn button-container
|
(defn button-container
|
||||||
[show-keyboard? keyboard-shown show-background? keyboard-height children]
|
[show-keyboard? keyboard-shown show-background? keyboard-height children]
|
||||||
(let [height (reagent/atom 0)]
|
(let [height (reagent/atom 0)]
|
||||||
|
@ -109,23 +78,26 @@
|
||||||
#(reset! show-keyboard? false))
|
#(reset! show-keyboard? false))
|
||||||
{:keys [image-path display-name color]} onboarding-profile-data
|
{:keys [image-path display-name color]} onboarding-profile-data
|
||||||
full-name (reagent/atom display-name)
|
full-name (reagent/atom display-name)
|
||||||
validation-msg (reagent/atom (validation-message
|
validation-msg (reagent/atom
|
||||||
@full-name))
|
(profile-validator/validation-name
|
||||||
|
@full-name))
|
||||||
on-change-text (fn [s]
|
on-change-text (fn [s]
|
||||||
(reset! validation-msg (validation-message
|
(reset! validation-msg
|
||||||
s))
|
(profile-validator/validation-name
|
||||||
|
s))
|
||||||
(reset! full-name (string/trim s)))
|
(reset! full-name (string/trim s)))
|
||||||
custom-color (reagent/atom (or color
|
custom-color (reagent/atom (or color
|
||||||
c/profile-default-color))
|
c/profile-default-color))
|
||||||
profile-pic (reagent/atom image-path)
|
profile-pic (reagent/atom image-path)
|
||||||
on-change-profile-pic #(reset! profile-pic %)
|
on-change-profile-pic #(reset! profile-pic %)
|
||||||
on-change #(reset! custom-color %)]
|
on-change #(reset! custom-color %)]
|
||||||
(let [name-too-short? (length-not-valid @full-name)
|
(let [name-too-short? (profile-validator/name-too-short? @full-name)
|
||||||
valid-name? (and (not @validation-msg) (not name-too-short?))
|
valid-name? (and (not @validation-msg) (not name-too-short?))
|
||||||
info-message (if @validation-msg
|
info-message (if @validation-msg
|
||||||
@validation-msg
|
@validation-msg
|
||||||
(i18n/label :t/minimum-characters
|
(i18n/label :t/minimum-characters
|
||||||
{:min-chars min-length}))
|
{:min-chars
|
||||||
|
profile-validator/min-length}))
|
||||||
info-type (cond @validation-msg :error
|
info-type (cond @validation-msg :error
|
||||||
name-too-short? :default
|
name-too-short? :default
|
||||||
:else :success)
|
:else :success)
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
(ns status-im.contexts.profile.edit.header.view
|
||||||
|
(:require [quo.core :as quo]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[status-im.common.not-implemented :as not-implemented]
|
||||||
|
[status-im.contexts.profile.edit.style :as style]
|
||||||
|
[status-im.contexts.profile.utils :as profile.utils]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
[]
|
||||||
|
(let [profile (rf/sub [:profile/profile-with-image])
|
||||||
|
full-name (profile.utils/displayed-name profile)
|
||||||
|
profile-picture (profile.utils/photo profile)]
|
||||||
|
[rn/view
|
||||||
|
{:key :edit-profile
|
||||||
|
:style style/screen-container}
|
||||||
|
[quo/text-combinations {:title (i18n/label :t/edit-profile)}]
|
||||||
|
[rn/view style/avatar-wrapper
|
||||||
|
[quo/user-avatar
|
||||||
|
{:full-name full-name
|
||||||
|
:profile-picture profile-picture
|
||||||
|
:status-indicator? false
|
||||||
|
:ring? true
|
||||||
|
:size :big}]
|
||||||
|
[quo/button
|
||||||
|
{:on-press not-implemented/alert
|
||||||
|
:container-style style/camera-button
|
||||||
|
:icon-only? true
|
||||||
|
:type :grey
|
||||||
|
:background :photo
|
||||||
|
:size 32}
|
||||||
|
:i/camera]]]))
|
|
@ -0,0 +1,62 @@
|
||||||
|
(ns status-im.contexts.profile.edit.list-items
|
||||||
|
(:require [quo.foundations.colors :as colors]
|
||||||
|
[status-im.common.not-implemented :as not-implemented]
|
||||||
|
[status-im.contexts.profile.edit.style :as style]
|
||||||
|
[status-im.contexts.profile.utils :as profile.utils]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn items
|
||||||
|
[theme]
|
||||||
|
(let [profile (rf/sub [:profile/profile-with-image])
|
||||||
|
customization-color (rf/sub [:profile/customization-color])
|
||||||
|
full-name (profile.utils/displayed-name profile)]
|
||||||
|
[{:label (i18n/label :t/profile)
|
||||||
|
:items [{:title (i18n/label :t/name)
|
||||||
|
:on-press #(rf/dispatch [:open-modal :edit-name])
|
||||||
|
:blur? true
|
||||||
|
:label :text
|
||||||
|
:label-props full-name
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}
|
||||||
|
{:title (i18n/label :t/bio)
|
||||||
|
:on-press not-implemented/alert
|
||||||
|
:blur? true
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}
|
||||||
|
{:title (i18n/label :t/accent-colour)
|
||||||
|
:on-press not-implemented/alert
|
||||||
|
:label :color
|
||||||
|
:label-props (colors/resolve-color customization-color theme)
|
||||||
|
:blur? true
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}]}
|
||||||
|
|
||||||
|
{:label (i18n/label :t/showcase)
|
||||||
|
:items [{:title (i18n/label :t/communities)
|
||||||
|
:on-press not-implemented/alert
|
||||||
|
:blur? true
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}
|
||||||
|
{:title (i18n/label :t/accounts)
|
||||||
|
:on-press not-implemented/alert
|
||||||
|
:blur? true
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}
|
||||||
|
{:title (i18n/label :t/collectibles)
|
||||||
|
:on-press not-implemented/alert
|
||||||
|
:blur? true
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}
|
||||||
|
{:title (i18n/label :t/assets)
|
||||||
|
:on-press not-implemented/alert
|
||||||
|
:blur? true
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}]}
|
||||||
|
|
||||||
|
{:label (i18n/label :t/on-the-web)
|
||||||
|
:items [{:title (i18n/label :t/links)
|
||||||
|
:on-press not-implemented/alert
|
||||||
|
:blur? true
|
||||||
|
:action :arrow
|
||||||
|
:container-style style/item-container}]}]))
|
|
@ -0,0 +1,18 @@
|
||||||
|
(ns status-im.contexts.profile.edit.name.events
|
||||||
|
(:require [utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn edit-profile-name
|
||||||
|
[{:keys [db]} [name]]
|
||||||
|
{:db (assoc-in db [:profile/profile :display-name] name)
|
||||||
|
:fx [[:json-rpc/call
|
||||||
|
[{:method "wakuext_setDisplayName"
|
||||||
|
:params [name]
|
||||||
|
:on-success (fn []
|
||||||
|
(rf/dispatch [:navigate-back])
|
||||||
|
(rf/dispatch [:toasts/upsert
|
||||||
|
{:type :positive
|
||||||
|
:theme :dark
|
||||||
|
:text (i18n/label :t/name-updated)}]))}]]]})
|
||||||
|
|
||||||
|
(rf/reg-event-fx :profile/edit-name edit-profile-name)
|
|
@ -0,0 +1,15 @@
|
||||||
|
(ns status-im.contexts.profile.edit.name.events-test
|
||||||
|
(:require [cljs.test :refer [deftest is]]
|
||||||
|
matcher-combinators.test
|
||||||
|
[status-im.contexts.profile.edit.name.events :as sut]))
|
||||||
|
|
||||||
|
(deftest edit-name-test
|
||||||
|
(let [new-name "John Doe"
|
||||||
|
cofx {:db {:profile/profile {:display-name "Old name"}}}
|
||||||
|
expected {:db {:profile/profile {:display-name new-name}}
|
||||||
|
:fx [[:json-rpc/call
|
||||||
|
[{:method "wakuext_setDisplayName"
|
||||||
|
:params [name]
|
||||||
|
:on-success fn?}]]]}]
|
||||||
|
(is (match? expected
|
||||||
|
(sut/edit-profile-name cofx [new-name])))))
|
|
@ -0,0 +1,17 @@
|
||||||
|
(ns status-im.contexts.profile.edit.name.style)
|
||||||
|
|
||||||
|
(defn page-wrapper
|
||||||
|
[insets]
|
||||||
|
{:padding-top (:top insets)
|
||||||
|
:padding-bottom (:bottom insets)
|
||||||
|
:padding-horizontal 1
|
||||||
|
:flex 1})
|
||||||
|
|
||||||
|
(def screen-container
|
||||||
|
{:flex 1
|
||||||
|
:padding-top 14
|
||||||
|
:padding-horizontal 20
|
||||||
|
:justify-content :space-between})
|
||||||
|
|
||||||
|
(def button-wrapper
|
||||||
|
{:margin-vertical 12})
|
|
@ -0,0 +1,72 @@
|
||||||
|
(ns status-im.contexts.profile.edit.name.view
|
||||||
|
(:require [clojure.string :as string]
|
||||||
|
[quo.core :as quo]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[react-native.safe-area :as safe-area]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[status-im.common.validation.profile :as profile-validator]
|
||||||
|
[status-im.constants :as constants]
|
||||||
|
[status-im.contexts.profile.edit.name.style :as style]
|
||||||
|
[status-im.contexts.profile.utils :as profile.utils]
|
||||||
|
[utils.debounce :as debounce]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
[]
|
||||||
|
(let [insets (safe-area/get-insets)
|
||||||
|
profile (rf/sub [:profile/profile-with-image])
|
||||||
|
customization-color (rf/sub [:profile/customization-color])
|
||||||
|
display-name (profile.utils/displayed-name profile)
|
||||||
|
full-name (reagent/atom display-name)
|
||||||
|
error-msg (reagent/atom nil)
|
||||||
|
typing? (reagent/atom false)
|
||||||
|
validate-name (debounce/debounce (fn [name]
|
||||||
|
(reset! error-msg
|
||||||
|
(profile-validator/validation-name name))
|
||||||
|
(reset! typing? false))
|
||||||
|
300)
|
||||||
|
on-change-text (fn [s]
|
||||||
|
(reset! typing? true)
|
||||||
|
(reset! full-name s)
|
||||||
|
(validate-name s))]
|
||||||
|
(fn []
|
||||||
|
[quo/overlay
|
||||||
|
{:type :shell
|
||||||
|
:container-style (style/page-wrapper insets)}
|
||||||
|
[quo/page-nav
|
||||||
|
{:key :header
|
||||||
|
:background :blur
|
||||||
|
:icon-name :i/arrow-left
|
||||||
|
:on-press #(rf/dispatch [:navigate-back])}]
|
||||||
|
[rn/keyboard-avoiding-view
|
||||||
|
{:key :content
|
||||||
|
:style style/screen-container}
|
||||||
|
[rn/view {:style {:gap 22}}
|
||||||
|
[quo/text-combinations {:title (i18n/label :t/name)}]
|
||||||
|
[quo/input
|
||||||
|
{:theme :dark
|
||||||
|
:blur? true
|
||||||
|
:error? (not (string/blank? @error-msg))
|
||||||
|
:container-style {:margin-bottom -11}
|
||||||
|
:default-value @full-name
|
||||||
|
:auto-focus true
|
||||||
|
:char-limit constants/profile-name-max-length
|
||||||
|
:label (i18n/label :t/profile-name)
|
||||||
|
:on-change-text on-change-text}]
|
||||||
|
(when-not (string/blank? @error-msg)
|
||||||
|
[quo/info-message
|
||||||
|
{:type :error
|
||||||
|
:size :default
|
||||||
|
:icon :i/info}
|
||||||
|
@error-msg])]
|
||||||
|
[rn/view {:style style/button-wrapper}
|
||||||
|
[quo/button
|
||||||
|
{:type :primary
|
||||||
|
:customization-color customization-color
|
||||||
|
:on-press (fn []
|
||||||
|
(rf/dispatch [:profile/edit-name @full-name]))
|
||||||
|
:disabled? (boolean (or @typing?
|
||||||
|
(string/blank? @full-name)
|
||||||
|
(not (string/blank? @error-msg))))}
|
||||||
|
(i18n/label :t/save-name)]]]])))
|
|
@ -0,0 +1,25 @@
|
||||||
|
(ns status-im.contexts.profile.edit.style)
|
||||||
|
|
||||||
|
(defn page-wrapper
|
||||||
|
[inset]
|
||||||
|
{:padding-top inset
|
||||||
|
:padding-horizontal 1})
|
||||||
|
|
||||||
|
(def screen-container
|
||||||
|
{:padding-top 14
|
||||||
|
:padding-horizontal 20})
|
||||||
|
|
||||||
|
(def avatar-wrapper
|
||||||
|
{:width 88
|
||||||
|
:margin-top 22
|
||||||
|
:margin-bottom 12})
|
||||||
|
|
||||||
|
(def camera-button
|
||||||
|
{:position :absolute
|
||||||
|
:border-radius 16
|
||||||
|
:overflow :hidden
|
||||||
|
:right 0
|
||||||
|
:bottom 0})
|
||||||
|
|
||||||
|
(def item-container
|
||||||
|
{:padding-top 14})
|
|
@ -0,0 +1,48 @@
|
||||||
|
(ns status-im.contexts.profile.edit.view
|
||||||
|
(:require [quo.core :as quo]
|
||||||
|
[quo.theme :as quo.theme]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[react-native.safe-area :as safe-area]
|
||||||
|
[status-im.common.not-implemented :as not-implemented]
|
||||||
|
[status-im.contexts.profile.edit.header.view :as header]
|
||||||
|
[status-im.contexts.profile.edit.list-items :as edit.items]
|
||||||
|
[status-im.contexts.profile.edit.style :as style]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn- item-view
|
||||||
|
[data]
|
||||||
|
[quo/category
|
||||||
|
{:container-style {:padding-bottom 9.5}
|
||||||
|
:list-type :settings
|
||||||
|
:blur? true
|
||||||
|
:label (:label data)
|
||||||
|
:data (:items data)}])
|
||||||
|
|
||||||
|
(defn- get-item-layout
|
||||||
|
[_ index]
|
||||||
|
#js {:length 100 :offset (* 100 index) :index index})
|
||||||
|
|
||||||
|
(defn internal-view
|
||||||
|
[theme]
|
||||||
|
(let [insets (safe-area/get-insets)]
|
||||||
|
[quo/overlay
|
||||||
|
{:type :shell
|
||||||
|
:container-style (style/page-wrapper (:top insets))}
|
||||||
|
[quo/page-nav
|
||||||
|
{:key :header
|
||||||
|
:background :blur
|
||||||
|
:icon-name :i/arrow-left
|
||||||
|
:on-press #(rf/dispatch [:navigate-back])
|
||||||
|
:right-side [{:icon-name :i/reveal :on-press not-implemented/alert}]}]
|
||||||
|
[rn/flat-list
|
||||||
|
{:key :list
|
||||||
|
:header [header/view]
|
||||||
|
:data (edit.items/items theme)
|
||||||
|
:key-fn :label
|
||||||
|
:get-item-layout get-item-layout
|
||||||
|
:initial-num-to-render 3
|
||||||
|
:max-to-render-per-batch 3
|
||||||
|
:shows-vertical-scroll-indicator false
|
||||||
|
:render-fn item-view}]]))
|
||||||
|
|
||||||
|
(def view (quo.theme/with-theme internal-view))
|
|
@ -2,6 +2,7 @@
|
||||||
(:require
|
(:require
|
||||||
[native-module.core :as native-module]
|
[native-module.core :as native-module]
|
||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
|
[status-im.contexts.profile.edit.name.events]
|
||||||
[status-im.contexts.profile.login.events :as profile.login]
|
[status-im.contexts.profile.login.events :as profile.login]
|
||||||
[status-im.contexts.profile.rpc :as profile.rpc]
|
[status-im.contexts.profile.rpc :as profile.rpc]
|
||||||
[status-im.navigation.events :as navigation]
|
[status-im.navigation.events :as navigation]
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
(def items
|
(def items
|
||||||
[[{:title (i18n/label :t/edit-profile)
|
[[{:title (i18n/label :t/edit-profile)
|
||||||
:on-press not-implemented/alert
|
:on-press #(rf/dispatch [:open-modal :edit-profile])
|
||||||
:image-props :i/edit
|
:image-props :i/edit
|
||||||
:image :icon
|
:image :icon
|
||||||
:blur? true
|
:blur? true
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
(ns status-im.integration-test.profile-test
|
||||||
|
(:require
|
||||||
|
[cljs.test :refer [deftest is]]
|
||||||
|
[day8.re-frame.test :as rf-test]
|
||||||
|
[status-im.contexts.profile.utils :as profile.utils]
|
||||||
|
[test-helpers.integration :as h]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(deftest edit-profile-name-test
|
||||||
|
(h/log-headline :edit-profile-name-test)
|
||||||
|
(let [new-name "John Doe"]
|
||||||
|
(rf-test/run-test-async
|
||||||
|
(h/with-app-initialized
|
||||||
|
(h/with-account
|
||||||
|
(rf/dispatch [:profile/edit-name new-name])
|
||||||
|
(rf-test/wait-for
|
||||||
|
[:navigate-back]
|
||||||
|
(rf-test/wait-for
|
||||||
|
[:toasts/upsert]
|
||||||
|
(let [profile (rf/sub [:profile/profile])
|
||||||
|
display-name (profile.utils/displayed-name profile)]
|
||||||
|
(is (= new-name display-name))))))))))
|
|
@ -34,6 +34,8 @@
|
||||||
[status-im.contexts.preview.quo.component-preview.view :as component-preview]
|
[status-im.contexts.preview.quo.component-preview.view :as component-preview]
|
||||||
[status-im.contexts.preview.quo.main :as quo.preview]
|
[status-im.contexts.preview.quo.main :as quo.preview]
|
||||||
[status-im.contexts.preview.status-im.main :as status-im-preview]
|
[status-im.contexts.preview.status-im.main :as status-im-preview]
|
||||||
|
[status-im.contexts.profile.edit.name.view :as edit-name]
|
||||||
|
[status-im.contexts.profile.edit.view :as edit-profile]
|
||||||
[status-im.contexts.profile.profiles.view :as profiles]
|
[status-im.contexts.profile.profiles.view :as profiles]
|
||||||
[status-im.contexts.profile.settings.view :as settings]
|
[status-im.contexts.profile.settings.view :as settings]
|
||||||
[status-im.contexts.shell.activity-center.view :as activity-center]
|
[status-im.contexts.shell.activity-center.view :as activity-center]
|
||||||
|
@ -165,6 +167,14 @@
|
||||||
:on-focus [:onboarding/overlay-dismiss]
|
:on-focus [:onboarding/overlay-dismiss]
|
||||||
:component profiles/view}
|
:component profiles/view}
|
||||||
|
|
||||||
|
{:name :edit-profile
|
||||||
|
:options options/transparent-screen-options
|
||||||
|
:component edit-profile/view}
|
||||||
|
|
||||||
|
{:name :edit-name
|
||||||
|
:options options/transparent-screen-options
|
||||||
|
:component edit-name/view}
|
||||||
|
|
||||||
{:name :new-to-status
|
{:name :new-to-status
|
||||||
:options {:theme :dark
|
:options {:theme :dark
|
||||||
:layout options/onboarding-transparent-layout
|
:layout options/onboarding-transparent-layout
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
"back-up-your-seed-phrase": "Back up your seed phrase",
|
"back-up-your-seed-phrase": "Back up your seed phrase",
|
||||||
"balance": "Balance",
|
"balance": "Balance",
|
||||||
"begin-set-up": "Begin setup",
|
"begin-set-up": "Begin setup",
|
||||||
|
"bio": "Bio",
|
||||||
"biometric-auth-android-sensor-desc": "Touch sensor",
|
"biometric-auth-android-sensor-desc": "Touch sensor",
|
||||||
"biometric-auth-android-sensor-error-desc": "Failed",
|
"biometric-auth-android-sensor-error-desc": "Failed",
|
||||||
"biometric-auth-android-title": "Authentication Required",
|
"biometric-auth-android-title": "Authentication Required",
|
||||||
|
@ -874,6 +875,7 @@
|
||||||
"left": "left",
|
"left": "left",
|
||||||
"lets-go": "Let's go!",
|
"lets-go": "Let's go!",
|
||||||
"les-ulc": "LES/ULC",
|
"les-ulc": "LES/ULC",
|
||||||
|
"links": "Links",
|
||||||
"linked-on": "Linked on {{date}}",
|
"linked-on": "Linked on {{date}}",
|
||||||
"load-messages-before": "before {{date}}",
|
"load-messages-before": "before {{date}}",
|
||||||
"load-more-messages": "↓ Fetch more messages",
|
"load-more-messages": "↓ Fetch more messages",
|
||||||
|
@ -972,6 +974,7 @@
|
||||||
"multiaccounts-recover-enter-phrase-title": "Enter your seed phrase",
|
"multiaccounts-recover-enter-phrase-title": "Enter your seed phrase",
|
||||||
"multichain": "Multichain",
|
"multichain": "Multichain",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"name-updated": "Name updated",
|
||||||
"name-of-token": "The name of your token",
|
"name-of-token": "The name of your token",
|
||||||
"need-help": "Need help?",
|
"need-help": "Need help?",
|
||||||
"new-to-status": "I’m new to Status",
|
"new-to-status": "I’m new to Status",
|
||||||
|
@ -1090,6 +1093,7 @@
|
||||||
"ok-got-it": "Okay, got it",
|
"ok-got-it": "Okay, got it",
|
||||||
"okay": "Okay",
|
"okay": "Okay",
|
||||||
"on": "On",
|
"on": "On",
|
||||||
|
"on-the-web": "On the web",
|
||||||
"only-mentions": "Only @mentions",
|
"only-mentions": "Only @mentions",
|
||||||
"open": "Open",
|
"open": "Open",
|
||||||
"open-home": "Open...",
|
"open-home": "Open...",
|
||||||
|
@ -1176,6 +1180,8 @@
|
||||||
"product-information": "Product Information",
|
"product-information": "Product Information",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"profile-details": "Profile details",
|
"profile-details": "Profile details",
|
||||||
|
"profile-name": "Profile name",
|
||||||
|
"profile-name-is-too-long": "Profile name is too long",
|
||||||
"public-chat": "Public chat",
|
"public-chat": "Public chat",
|
||||||
"public-chats": "Public chats",
|
"public-chats": "Public chats",
|
||||||
"public-group-status": "Public",
|
"public-group-status": "Public",
|
||||||
|
@ -1236,6 +1242,7 @@
|
||||||
"revoke-access": "Revoke access",
|
"revoke-access": "Revoke access",
|
||||||
"rpc-url": "RPC URL",
|
"rpc-url": "RPC URL",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"save-name": "Save name",
|
||||||
"save-password": "Save password",
|
"save-password": "Save password",
|
||||||
"save-password-unavailable": "Set device passcode to save password",
|
"save-password-unavailable": "Set device passcode to save password",
|
||||||
"save-password-unavailable-android": "Save password is unavailable: your device may be rooted or lacks necessary security features.",
|
"save-password-unavailable-android": "Save password is unavailable: your device may be rooted or lacks necessary security features.",
|
||||||
|
@ -1287,6 +1294,7 @@
|
||||||
"sharing-copy-to-clipboard": "Copy",
|
"sharing-copy-to-clipboard": "Copy",
|
||||||
"share-logs": "Share logs",
|
"share-logs": "Share logs",
|
||||||
"sharing-share": "Share",
|
"sharing-share": "Share",
|
||||||
|
"showcase": "Showcase",
|
||||||
"show-less": "Show less",
|
"show-less": "Show less",
|
||||||
"show-more": "Show more",
|
"show-more": "Show more",
|
||||||
"show-qr": "Show QR code",
|
"show-qr": "Show QR code",
|
||||||
|
|
Loading…
Reference in New Issue