diff --git a/src/quo2/components/colors/color_picker/component_spec.cljs b/src/quo2/components/colors/color_picker/component_spec.cljs index 2831f1a176..cb138e0037 100644 --- a/src/quo2/components/colors/color_picker/component_spec.cljs +++ b/src/quo2/components/colors/color_picker/component_spec.cljs @@ -3,11 +3,13 @@ [reagent.core :as reagent] [test-helpers.component :as h])) +(def color-list [:blue :yellow :turquoise :copper :sky :camel :orange :army :pink :purple :magenta]) + (h/describe "color-picker" (h/test "color picker rendered" (h/render [color-picker/view]) (-> (h/expect (h/get-all-by-label-text :color-picker-item)) - (.toHaveLength 12))) + (.toHaveLength 11))) (h/test "clicks on a color item" (let [event (h/mock-fn)] (h/render [color-picker/view {:on-change #(event)}]) @@ -19,5 +21,11 @@ (h/render [color-picker/view {:on-change #(reset! selected %)}]) (h/fire-event :press (get (h/get-all-by-label-text :color-picker-item) 0)) (-> (h/expect @selected) - (.toStrictEqual :blue))))) + (.toStrictEqual :blue)))) + (h/test "all of the values of colors-list are rendered" + (h/render [color-picker/view]) + (js/Promise.all (map (fn [color] + (h/is-truthy (h/get-all-by-label-text color))) + color-list)))) + diff --git a/src/quo2/components/colors/color_picker/style.cljs b/src/quo2/components/colors/color_picker/style.cljs index 47952f0773..3df1061fd3 100644 --- a/src/quo2/components/colors/color_picker/style.cljs +++ b/src/quo2/components/colors/color_picker/style.cljs @@ -2,7 +2,7 @@ (:require [quo2.foundations.colors :as colors])) (def color-picker-container - {:max-width 338 + {:flex 1 :flex-direction :row :flex-wrap :wrap :justify-content :space-between}) @@ -11,14 +11,17 @@ {:flex-basis "100%" :height 10}) +(def color-button-common + {:width 48 + :height 48 + :border-width 4 + :border-radius 24 + :transform [{:rotate "45deg"}] + :border-color :transparent}) + (defn color-button [color selected?] - (merge {:width 48 - :height 48 - :border-width 4 - :border-radius 24 - :transform [{:rotate "45deg"}] - :border-color :transparent} + (merge color-button-common (when selected? {:border-top-color (colors/alpha color 0.4) :border-end-color (colors/alpha color 0.4) diff --git a/src/quo2/components/colors/color_picker/view.cljs b/src/quo2/components/colors/color_picker/view.cljs index ab126ae905..5e14e7c670 100644 --- a/src/quo2/components/colors/color_picker/view.cljs +++ b/src/quo2/components/colors/color_picker/view.cljs @@ -5,25 +5,27 @@ [reagent.core :as reagent] [quo2.components.colors.color-picker.style :as style])) -(def color-list [:blue :yellow :turquoise :copper :sky :camel :orange :army :pink :purple :magenta]) +;; TODO: using :no-color this to keep alignment of colors correct while b & w is being developed. +;; https://github.com/status-im/status-mobile/issues/15442 +(def color-list + [:blue :yellow :turquoise :copper :sky :camel :orange :army :pink :purple :magenta :no-color]) (defn picker-colors [blur?] - (concat (map (fn [color] - {:name color - :color (colors/custom-color-by-theme color (if blur? 60 50) 60)}) - color-list) - [{:name :yinyang - :color (colors/theme-colors (colors/custom-color :yin 50) - (colors/custom-color :yang 50)) - :secondary-color (colors/theme-colors (colors/custom-color :yang 50) - (colors/custom-color :yin 50))}])) + (map (fn [color] + {:name color + :color (colors/custom-color-by-theme color (if blur? 60 50) 60)}) + color-list)) (defn- on-change-handler [selected color-name on-change] (reset! selected color-name) (when on-change (on-change color-name))) +(defn empty-color-item + [] + [rn/view {:style style/color-button-common}]) + (defn- color-item [{:keys [name color @@ -32,20 +34,24 @@ on-press blur?]}] (let [border? (and (not blur?) (and secondary-color (not selected?)))] - [rn/touchable-opacity - {:style (style/color-button color selected?) - :accessibility-label :color-picker-item - :on-press #(on-press name)} - [rn/view - {:style (style/color-circle color border?)} - (when (and secondary-color (not selected?)) - [rn/view - {:style (style/secondary-overlay secondary-color border?)}]) - (when selected? - [icon/icon :i/check - {:size 20 - :color (or secondary-color - colors/white)}])]])) + (if (= :no-color name) + [empty-color-item] + [rn/touchable-opacity + {:style (style/color-button color selected?) + :accessibility-label :color-picker-item + :on-press #(on-press name)} + [rn/view + {:accessibile true + :accessibility-label name + :style (style/color-circle color border?)} + (when (and secondary-color (not selected?)) + [rn/view + {:style (style/secondary-overlay secondary-color border?)}]) + (when selected? + [icon/icon :i/check + {:size 20 + :color (or secondary-color + colors/white)}])]]))) (defn view "Options diff --git a/src/quo2/foundations/colors.cljs b/src/quo2/foundations/colors.cljs index c6dcd4ca08..b6a9162e7c 100644 --- a/src/quo2/foundations/colors.cljs +++ b/src/quo2/foundations/colors.cljs @@ -182,51 +182,48 @@ (def danger-50-opa-30 (alpha danger-50 0.3)) (def danger-50-opa-40 (alpha danger-50 0.4)) -;;;; Customization -;; Colors for customizing profiles and communities themes +;; Colors for customizing users account (def customization - {:primary {50 primary-50 ;; User can also use primary color as customisation color - 60 primary-60} - :purple {50 "#7140FD" - 60 "#5A33CA"} - :indigo {50 "#496289" - 60 "#3D5273"} - :turquoise {50 "#2A799B" - 60 "#22617C"} - :blue {50 "#2A4AF5" + {:blue {50 "#2A4AF5" 60 "#223BC4"} - :green {50 "#5BCC95" - 60 "#4CAB7D"} :yellow {50 "#F6B03C" 60 "#C58D30"} - :orange {50 "#FF7D46" - 60 "#CC6438"} - :red {50 "#F46666" - 60 "#CD5656"} - :pink {50 "#F66F8F" - 60 "#C55972"} - :brown {50 "#99604D" - 60 "#805141"} - :sky {50 "#1992D7" - 60 "#1475AC"} - :army {50 "#216266" - 60 "#1A4E52"} - :magenta {50 "#EC266C" - 60 "#BD1E56"} + :turquoise {50 "#2A799B" + 60 "#22617C"} :copper {50 "#CB6256" 60 "#A24E45"} + :sky {50 "#1992D7" + 60 "#1475AC"} :camel {50 "#C78F67" 60 "#9F7252"} - :yin {50 "#09101C" - 60 "#1D232E"} - :yang {50 "#FFFFFF" - 60 "#EBEBEB"} - :beige {50 "#CAAE93" - 60 "#AA927C"}}) + :orange {50 "#FF7D46" + 60 "#CC6438"} + :army {50 "#216266" + 60 "#1A4E52"} + :pink {50 "#F66F8F" + 60 "#C55972"} + :purple {50 "#7140FD" + 60 "#5A33CA"} + :magenta {50 "#EC266C" + 60 "#BD1E56"}}) (def colors-map - (merge {:danger {50 danger-50 + (merge {:primary {50 primary-50 ;; User can also use primary color as customisation color + 60 primary-60} + :beige {50 "#CAAE93" + 60 "#AA927C"} + :green {50 "#5BCC95" + 60 "#4CAB7D"} + :brown {50 "#99604D" + 60 "#805141"} + :red {50 "#F46666" + 60 "#CD5656"} + :magenta {50 "#EC266C" + 60 "#BD1E56"} + :indigo {50 "#496289" + 60 "#3D5273"} + :danger {50 danger-50 60 danger-60} :success {50 success-50 60 success-60}} @@ -244,9 +241,7 @@ ([color suffix opacity] (let [color-keyword (keyword color) base-color (get-in colors-map - [(if (= color-keyword :yinyang) - (if (theme/dark?) :yang :yin) - (keyword color)) suffix])] + [color-keyword suffix])] (if opacity (alpha base-color (/ opacity 100)) base-color)))))) (defn custom-color-by-theme diff --git a/src/status_im2/contexts/onboarding/create_profile/style.cljs b/src/status_im2/contexts/onboarding/create_profile/style.cljs index 3d55eccfc6..ce5e7ec3e5 100644 --- a/src/status_im2/contexts/onboarding/create_profile/style.cljs +++ b/src/status_im2/contexts/onboarding/create_profile/style.cljs @@ -12,16 +12,15 @@ {:width "100%" :padding-left 20 :padding-right 20 - :padding-top (if platform/android? 0 12) + :padding-top 12 :align-self :flex-end :height 64}) -(def blur-button-container - (merge button-container - {:background-color colors/neutral-80-opa-1-blur})) - (def view-button-container - (merge button-container {:margin-bottom 24})) + (merge button-container {:margin-bottom 34})) + +(def blur-button-container + (merge button-container (when platform/android? {:padding-bottom 12}))) (def page-container {:position :absolute @@ -37,7 +36,7 @@ (def title {:color colors/white :margin-top 12 - :margin-bottom 20}) + :margin-bottom 18}) (def color-title {:color colors/white-70-blur diff --git a/src/status_im2/contexts/onboarding/create_profile/view.cljs b/src/status_im2/contexts/onboarding/create_profile/view.cljs index af66c42d1f..18d45ed245 100644 --- a/src/status_im2/contexts/onboarding/create_profile/view.cljs +++ b/src/status_im2/contexts/onboarding/create_profile/view.cljs @@ -13,8 +13,12 @@ [utils.re-frame :as rf] [oops.core :as oops] [react-native.blur :as blur] - [status-im2.constants :as c])) + [status-im2.constants :as c] + [react-native.platform :as platform])) +;; 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 @@ -23,12 +27,10 @@ (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 special-characters-regex (new js/RegExp #"[^a-zA-Z\d\s-._]" "i")) -(defn has-special-characters [s] (re-find special-characters-regex s)) +(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 s)) min-length)) -(def valid-regex (new js/RegExp #"^[\w-\s]{5,24}$" "i")) -(defn valid-name [s] (re-find valid-regex s)) +(defn length-not-valid [s] (< (count (string/trim (str s))) min-length)) (defn validation-message [s] @@ -39,18 +41,22 @@ (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)}) - (length-not-valid s) (i18n/label :t/name-must-have-at-least-characters - {:min-chars min-length}) - (not (valid-name s)) (i18n/label :t/name-is-not-valid) :else nil)) (defn button-container [keyboard-shown? children] [rn/view {:style {:margin-top :auto}} (if keyboard-shown? - [blur/ios-view {:style style/blur-button-container} + [blur/ios-view + {:blur-amount 34 + :blur-type :transparent + :overlay-color :transparent + :background-color (if platform/android? colors/neutral-100 colors/neutral-80-opa-1-blur) + :style style/blur-button-container} children] [rn/view {:style style/view-button-container} children])]) @@ -58,13 +64,17 @@ (defn- f-page [{:keys [onboarding-profile-data navigation-bar-top]}] (reagent/with-let [keyboard-shown? (reagent/atom false) - will-show-listener (oops/ocall rn/keyboard + show-listener (oops/ocall rn/keyboard "addListener" - "keyboardWillShow" + (if platform/android? + "keyboardDidShow" + "keyboardWillShow") #(reset! keyboard-shown? true)) - will-hide-listener (oops/ocall rn/keyboard + hide-listener (oops/ocall rn/keyboard "addListener" - "keyboardWillHide" + (if platform/android? + "keyboardDidHide" + "keyboardWillHide") #(reset! keyboard-shown? false)) {:keys [image-path display-name color]} onboarding-profile-data full-name (reagent/atom display-name) @@ -79,82 +89,92 @@ profile-pic (reagent/atom image-path) on-change-profile-pic #(reset! profile-pic %) on-change #(reset! custom-color %)] - [rn/view {:style style/page-container} - [navigation-bar/navigation-bar {:top navigation-bar-top}] - [rn/scroll-view - {:keyboard-should-persist-taps :always - :content-container-style {:flex-grow 1}} + (let [name-too-short? (length-not-valid @full-name) + valid-name? (and (not @validation-msg) (not name-too-short?)) + info-message (if @validation-msg + @validation-msg + (i18n/label :t/minimum-characters + {:min-chars min-length})) + info-type (cond @validation-msg :error + name-too-short? :default + :else :success)] [rn/view {:style style/page-container} - [rn/view - {:style style/content-container} - [quo/text - {:size :heading-1 - :weight :semi-bold - :style style/title} (i18n/label :t/create-profile)] - [rn/view - {:style style/input-container} + [navigation-bar/navigation-bar {:top navigation-bar-top}] + [rn/scroll-view + {:content-container-style {:flexGrow 1}} + [rn/view {:style style/page-container} [rn/view - {:style style/profile-input-container} - [quo/profile-input - {:customization-color @custom-color - :placeholder (i18n/label :t/your-name) - :on-press (fn [] - (rf/dispatch [:dismiss-keyboard]) - (rf/dispatch - [:show-bottom-sheet - {:override-theme :dark - :content - (fn [] - [method-menu/view on-change-profile-pic])}])) - :image-picker-props {:profile-picture (when @profile-pic {:uri @profile-pic}) - :full-name (if (seq @full-name) - @full-name - (i18n/label :t/your-name)) - :customization-color @custom-color} - :title-input-props {:default-value @full-name - :auto-focus true - :max-length c/profile-name-max-length - :on-change-text on-change-text}}]] - (when @validation-msg + {:style style/content-container} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/title} (i18n/label :t/create-profile)] + [rn/view + {:style style/input-container} + [rn/view + {:style style/profile-input-container} + [quo/profile-input + {:customization-color @custom-color + :placeholder (i18n/label :t/your-name) + :on-press (fn [] + (rf/dispatch [:dismiss-keyboard]) + (rf/dispatch + [:show-bottom-sheet + {:override-theme :dark + :content + (fn [] + [method-menu/view on-change-profile-pic])}])) + :image-picker-props {:profile-picture (when @profile-pic {:uri @profile-pic}) + :full-name (if (seq @full-name) + @full-name + (i18n/label :t/your-name)) + :customization-color @custom-color} + :title-input-props {:default-value @full-name + :auto-focus true + :max-length c/profile-name-max-length + :on-change-text on-change-text}}]] + [quo/info-message - {:type :error - :size :default - :icon :i/info - :style style/info-message} - @validation-msg]) - [quo/text - {:size :paragraph-2 - :weight :medium - :style style/color-title} - (i18n/label :t/accent-colour)] - [quo/color-picker - {:blur? true - :default-selected? :blue - :selected @custom-color - :on-change on-change}]]]]] - [rn/keyboard-avoiding-view - {:style {:position :absolute - :top 0 - :bottom 0 - :left 0 - :right 0} - :pointer-events :box-none} - [button-container @keyboard-shown? - [quo/button - {:accessibility-label :submit-create-profile-button - :type :primary - :override-background-color (colors/custom-color @custom-color 60) - :on-press (fn [] - (rf/dispatch [:onboarding-2/profile-data-set - {:image-path @profile-pic - :display-name @full-name - :color @custom-color}])) - :style style/continue-button - :disabled (or (not (seq @full-name)) @validation-msg)} - (i18n/label :t/continue)]]]] + {:type info-type + :size :default + :icon (if valid-name? :i/positive-state :i/info) + :text-color (when (= :default info-type) colors/white-70-blur) + :icon-color (when (= :default info-type) colors/white-70-blur) + :style style/info-message} + info-message] + [quo/text + {:size :paragraph-2 + :weight :medium + :style style/color-title} + (i18n/label :t/accent-colour)] + [quo/color-picker + {:blur? true + :default-selected? :blue + :selected @custom-color + :on-change on-change}]]]]] + [rn/keyboard-avoiding-view + {:style {:position :absolute + :top 0 + :bottom 0 + :left 0 + :right 0} + :pointer-events :box-none} + [button-container @keyboard-shown? + [quo/button + {:accessibility-label :submit-create-profile-button + :type :primary + :override-background-color (colors/custom-color @custom-color 60) + :on-press (fn [] + (rf/dispatch [:onboarding-2/profile-data-set + {:image-path @profile-pic + :display-name @full-name + :color @custom-color}])) + :style style/continue-button + :disabled (or (not valid-name?) (not (seq @full-name)))} + (i18n/label :t/continue)]]]]) (finally - (oops/ocall will-show-listener "remove") - (oops/ocall will-hide-listener "remove")))) + (oops/ocall show-listener "remove") + (oops/ocall hide-listener "remove")))) (defn create-profile [] diff --git a/translations/en.json b/translations/en.json index 98069a273f..b3f98306f6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -493,6 +493,7 @@ "enable-notifications-sub-title": "Receive notifications when somebody sends you a message or crypto to your wallet", "encrypt-with-password": "Encrypt with password", "ending-not-allowed": "{{ending}} ending is not allowed", + "ends-with-space": "Cannot end with space", "ens-10-SNT": "10 SNT", "ens-add-username": "Add username", "ens-agree-to": "Agree to ", @@ -1296,6 +1297,7 @@ "start-group-chat": "Start group chat", "start-new-chat": "Start new chat", "start-using-status": "Start using Status", + "start-with-space": "Cannot start with space", "status": "Status", "status-confirmed": "Confirmed", "status-hardwallet": "Status hardwallet", @@ -2110,8 +2112,7 @@ "camera-permission-denied": "Permission denied", "enable-biometrics": "Enable biometrics", "use-biometrics": "Use biometrics to fill in your password", - "name-must-have-at-least-characters": "Name must have at least {{min-chars}} characters", - "name-is-not-valid": "Name is not valid", + "minimum-characters": "Minimum {{min-chars}} characters", "no-communities": "No communities", "no-communities-description-strikethrough": "Never", "no-communities-description": "go full nyan, find your community",