fix: create password for small screen (#20645)

* fix: create password for small screen

* feat: use floating button page

Signed-off-by: yqrashawn <namy.19@gmail.com>

* fix: use keyboard will show for ios

Signed-off-by: yqrashawn <namy.19@gmail.com>

* fix: safe area bottom on devices without physical home button

Signed-off-by: yqrashawn <namy.19@gmail.com>

* feat(create-password): tips and checkbox stick with confirm button

* fix: floating button blur after rebase

* fix(floating-button): absolute content avoid keyboard view

---------

Signed-off-by: yqrashawn <namy.19@gmail.com>
This commit is contained in:
yqrashawn 2024-07-29 13:41:15 +08:00 committed by GitHub
parent 525609f0af
commit 55c620e59d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 248 additions and 171 deletions

View File

@ -4,11 +4,37 @@
[oops.core :as oops] [oops.core :as oops]
[react-native.core :as rn])) [react-native.core :as rn]))
(defn- add-keyboard-listener
[listener callback]
(oops/ocall rn/keyboard "addListener" listener callback))
(defn use-keyboard (defn use-keyboard
[] []
(let [kb (.useKeyboard hooks)] (let [[keyboard-height set-keyboard-height] (rn/use-state 0)
{:keyboard-shown (.-keyboardShown ^js kb) [did-show? set-did-show] (rn/use-state false)
:keyboard-height (.-keyboardHeight ^js kb)})) [will-show? set-will-show] (rn/use-state false)]
(rn/use-mount
(fn []
(let [will-show-listener (add-keyboard-listener "keyboardWillShow" #(set-will-show true))
did-show-listener (add-keyboard-listener "keyboardDidShow"
(fn [e]
(set-did-show true)
(set-keyboard-height
(oops/oget e "endCoordinates.height"))))
will-hide-listener (add-keyboard-listener "keyboardWillHide" #(set-will-show false))
did-hide-listener (add-keyboard-listener "keyboardDidHide"
(fn [e]
(set-did-show false)
(when e
(oops/oget e "endCoordinates.height"))))]
(fn []
(oops/ocall will-show-listener "remove")
(oops/ocall did-show-listener "remove")
(oops/ocall will-hide-listener "remove")
(oops/ocall did-hide-listener "remove")))))
{:keyboard-shown did-show?
:keyboard-will-show? will-show?
:keyboard-height keyboard-height}))
(defn use-back-handler (defn use-back-handler
[handler] [handler]

View File

@ -3,21 +3,23 @@
[react-native.safe-area :as safe-area])) [react-native.safe-area :as safe-area]))
(defn content-container (defn content-container
[blur? keyboard-shown?] [blur? keyboard-shown? {:keys [padding-vertical padding-horizontal]}]
(let [margin-bottom (if keyboard-shown? 0 (safe-area/get-bottom))] (let [margin-bottom (if keyboard-shown? 0 (safe-area/get-bottom))]
(cond-> {:margin-top :auto (cond-> {:margin-top :auto
:overflow :hidden :overflow :hidden
:margin-bottom margin-bottom :margin-bottom margin-bottom
:padding-vertical 12 :padding-vertical (or padding-vertical 12)
:padding-horizontal 20} :padding-horizontal (or padding-horizontal 20)}
blur? (dissoc :padding-vertical :padding-horizontal)))) blur? (dissoc :padding-vertical :padding-horizontal))))
(defn blur-inner-container (defn blur-inner-container
[theme shell-overlay?] [{:keys [theme shell-overlay? padding-vertical padding-horizontal background-color]}]
{:background-color (colors/theme-colors colors/white-70-blur {:background-color (or background-color
(if shell-overlay? (colors/theme-colors
colors/neutral-80-opa-80-blur colors/white-70-blur
colors/neutral-95-opa-70-blur) (if shell-overlay?
theme) colors/neutral-80-opa-80-blur
:padding-vertical 12 colors/neutral-95-opa-70-blur)
:padding-horizontal 20}) theme))
:padding-vertical (or padding-vertical 12)
:padding-horizontal (or padding-horizontal 20)})

View File

@ -6,20 +6,25 @@
[status-im.common.floating-button-page.floating-container.style :as style])) [status-im.common.floating-button-page.floating-container.style :as style]))
(defn- blur-container (defn- blur-container
[child shell-overlay?] [shell-overlay? blur-options child]
(let [theme (quo.theme/use-theme)] (let [theme (quo.theme/use-theme)]
[quo/blur [quo/blur
{:blur-amount 20 (or blur-options
:blur-type :transparent {:blur-amount 20
:overlay-color :transparent} :blur-type :transparent
[rn/view {:style (style/blur-inner-container theme shell-overlay?)} :overlay-color :transparent})
[rn/view
{:style (style/blur-inner-container (assoc
blur-options
:theme theme
:shell-overlay? shell-overlay?))}
child]])) child]]))
(defn view (defn view
[{:keys [on-layout keyboard-shown? blur? shell-overlay?]} child] [{:keys [on-layout keyboard-shown? blur? shell-overlay? blur-options]} child]
[rn/view [rn/view
{:style (style/content-container blur? keyboard-shown?) {:style (style/content-container blur? keyboard-shown? blur-options)
:on-layout on-layout} :on-layout on-layout}
(if blur? (if blur?
[blur-container child shell-overlay?] [blur-container shell-overlay? blur-options child]
child)]) child)])

View File

@ -13,3 +13,13 @@
:bottom 0 :bottom 0
:left 0 :left 0
:right 0}) :right 0})
(defn content-keyboard-avoiding-view
[{:keys [top bottom]}]
{:position :absolute
:top top
:left 0
:right 0
:bottom bottom})
(def scroll-view-container {:flex 1})

View File

@ -30,13 +30,15 @@
(reset! ratom height)))) (reset! ratom height))))
(defn- init-keyboard-listeners (defn- init-keyboard-listeners
[{:keys [on-did-show scroll-view-ref]}] [{:keys [on-did-show on-will-show scroll-view-ref]}]
(let [keyboard-will-show? (reagent/atom false) (let [keyboard-will-show? (reagent/atom false)
keyboard-did-show? (reagent/atom false) keyboard-did-show? (reagent/atom false)
add-listener (fn [listener callback] add-listener (fn [listener callback]
(oops/ocall rn/keyboard "addListener" listener callback)) (oops/ocall rn/keyboard "addListener" listener callback))
will-show-listener (add-listener "keyboardWillShow" will-show-listener (add-listener "keyboardWillShow"
#(reset! keyboard-will-show? true)) (fn [e]
(reset! keyboard-will-show? true)
(when on-will-show (on-will-show e))))
did-show-listener (add-listener "keyboardDidShow" did-show-listener (add-listener "keyboardDidShow"
(fn [e] (fn [e]
(reset! keyboard-did-show? true) (reset! keyboard-did-show? true)
@ -58,7 +60,8 @@
(defn view (defn view
[{:keys [header footer customization-color footer-container-padding header-container-style [{:keys [header footer customization-color footer-container-padding header-container-style
content-container-style gradient-cover? keyboard-should-persist-taps shell-overlay?] content-container-style gradient-cover? keyboard-should-persist-taps shell-overlay?
blur-options content-avoid-keyboard?]
:or {footer-container-padding (safe-area/get-top)}} :or {footer-container-padding (safe-area/get-top)}}
& children] & children]
(reagent/with-let [scroll-view-ref (atom nil) (reagent/with-let [scroll-view-ref (atom nil)
@ -69,29 +72,34 @@
content-container-height (reagent/atom 0) content-container-height (reagent/atom 0)
content-scroll-y (reagent/atom 0) content-scroll-y (reagent/atom 0)
keyboard-height (reagent/atom 0) keyboard-height (reagent/atom 0)
reset-keyboard-height #(reset! keyboard-height (oops/oget
%
"endCoordinates.height"))
{:keys [keyboard-will-show? {:keys [keyboard-will-show?
keyboard-did-show? keyboard-did-show?
remove-listeners]} (init-keyboard-listeners remove-listeners]} (init-keyboard-listeners
{:scroll-view-ref scroll-view-ref (cond-> {:scroll-view-ref scroll-view-ref}
:on-did-show platform/ios?
(fn [e] (assoc :on-will-show reset-keyboard-height)
(reset! keyboard-height (not platform/ios?)
(oops/oget e "endCoordinates.height")))}) (assoc :on-did-show reset-keyboard-height)))
set-header-height (set-height-on-layout header-height) set-header-height (set-height-on-layout header-height)
set-content-container-height (set-height-on-layout content-container-height) set-content-container-height (set-height-on-layout content-container-height)
set-footer-container-height (set-height-on-layout footer-container-height) set-footer-container-height (set-height-on-layout footer-container-height)
set-content-y-scroll (fn [event] set-content-y-scroll (fn [event]
(reset! content-scroll-y (reset! content-scroll-y
(oops/oget event "nativeEvent.contentOffset.y")))] (oops/oget event "nativeEvent.contentOffset.y")))
bottom-safe-area (safe-area/get-bottom)]
(let [keyboard-shown? (if platform/ios? @keyboard-will-show? @keyboard-did-show?) (let [keyboard-shown? (if platform/ios? @keyboard-will-show? @keyboard-did-show?)
footer-container-padding (+ footer-container-padding (rf/sub [:alert-banners/top-margin])) footer-container-padding (+ footer-container-padding (rf/sub [:alert-banners/top-margin]))
show-background? (show-background {:window-height window-height show-background? (show-background
:footer-container-height @footer-container-height {:window-height window-height
:keyboard-height @keyboard-height :footer-container-height @footer-container-height
:content-scroll-y @content-scroll-y :keyboard-height @keyboard-height
:content-container-height @content-container-height :content-scroll-y @content-scroll-y
:header-height @header-height :content-container-height @content-container-height
:keyboard-shown? keyboard-shown?})] :header-height @header-height
:keyboard-shown? keyboard-shown?})]
[:<> [:<>
(when gradient-cover? (when gradient-cover?
[quo/gradient-cover {:customization-color customization-color}]) [quo/gradient-cover {:customization-color customization-color}])
@ -100,20 +108,28 @@
{:on-layout set-header-height {:on-layout set-header-height
:style header-container-style} :style header-container-style}
header] header]
[gesture/scroll-view [(if content-avoid-keyboard? rn/keyboard-avoiding-view rn/view)
{:ref set-scroll-ref {:style
:on-scroll set-content-y-scroll (if content-avoid-keyboard?
:scroll-event-throttle 64 (style/content-keyboard-avoiding-view
:content-container-style {:flex-grow 1 {:top @header-height
:padding-bottom (when @keyboard-did-show? :bottom (if keyboard-shown?
@footer-container-height)} @footer-container-height
:always-bounce-vertical @keyboard-did-show? (+ bottom-safe-area @footer-container-height))})
:shows-vertical-scroll-indicator false style/scroll-view-container)}
:keyboard-should-persist-taps keyboard-should-persist-taps} [gesture/scroll-view
(into [rn/view {:ref set-scroll-ref
{:style content-container-style :on-scroll set-content-y-scroll
:on-layout set-content-container-height}] :scroll-event-throttle 64
children)] :content-container-style {:flex-grow 1}
:always-bounce-vertical @keyboard-did-show?
:automatically-adjust-keyboard-insets true
:shows-vertical-scroll-indicator false
:keyboard-should-persist-taps keyboard-should-persist-taps}
(into [rn/view
{:style content-container-style
:on-layout set-content-container-height}]
children)]]
[rn/keyboard-avoiding-view [rn/keyboard-avoiding-view
{:style style/keyboard-avoiding-view {:style style/keyboard-avoiding-view
:keyboard-vertical-offset (if platform/ios? footer-container-padding 0) :keyboard-vertical-offset (if platform/ios? footer-container-padding 0)
@ -121,8 +137,9 @@
[floating-container/view [floating-container/view
{:on-layout set-footer-container-height {:on-layout set-footer-container-height
:keyboard-shown? keyboard-shown? :keyboard-shown? keyboard-shown?
:blur? show-background? :blur-options blur-options
:shell-overlay? shell-overlay?} :shell-overlay? shell-overlay?
:blur? show-background?}
footer]]]]) footer]]]])
(finally (finally
(remove-listeners)))) (remove-listeners))))

View File

@ -2,8 +2,6 @@
(:require (:require
[quo.foundations.colors :as colors])) [quo.foundations.colors :as colors]))
(def flex-fill {:flex 1})
(def heading {:margin-bottom 20}) (def heading {:margin-bottom 20})
(def heading-subtitle {:color colors/white}) (def heading-subtitle {:color colors/white})
(def heading-title (assoc heading-subtitle :margin-bottom 8)) (def heading-title (assoc heading-subtitle :margin-bottom 8))
@ -14,23 +12,15 @@
(def space-between-inputs {:height 16}) (def space-between-inputs {:height 16})
(def password-tips (def password-tips
{:flex-direction :row {:flex-direction :row
:justify-content :space-between :justify-content :space-between
:margin-horizontal 20}) :padding-horizontal 20})
(def top-part (def form-container
{:margin-horizontal 20 {:justify-content :space-between
:margin-top 12}) :padding-top 12
:padding-horizontal 20})
(def bottom-part (def disclaimer-container {:padding-horizontal 20})
{:flex 1 (def footer-container {:padding-bottom 12})
:margin-top 12 (def footer-button-container {:margin-top 20 :padding-horizontal 20})
:justify-content :flex-end})
(def disclaimer-container
{:margin-horizontal 20
:margin-vertical 4})
(def button-container
{:margin-horizontal 20
:margin-vertical 12})

View File

@ -1,11 +1,11 @@
(ns status-im.contexts.onboarding.create-password.view (ns status-im.contexts.onboarding.create-password.view
(:require (:require
[oops.core :refer [ocall]]
[quo.core :as quo] [quo.core :as quo]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[reagent.core :as reagent] [status-im.common.floating-button-page.view :as floating-button]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.onboarding.create-password.style :as style] [status-im.contexts.onboarding.create-password.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
@ -94,7 +94,7 @@
[quo/tips {:completed? symbols?} [quo/tips {:completed? symbols?}
(i18n/label :t/password-creation-tips-4)]]]) (i18n/label :t/password-creation-tips-4)]]])
(defn password-validations (defn validate-password
[password] [password]
(let [validations (juxt utils.string/has-lower-case? (let [validations (juxt utils.string/has-lower-case?
utils.string/has-upper-case? utils.string/has-upper-case?
@ -111,68 +111,25 @@
(filter true?) (filter true?)
count)) count))
(defn password-form (defn- use-password-checks
[] [password]
(let [password (reagent/atom "") (rn/use-memo
repeat-password (reagent/atom "") (fn []
accepts-disclaimer? (reagent/atom false) (let [{:keys [long-enough?]
focused-input (reagent/atom nil) :as validations} (validate-password password)]
show-password-validation? (reagent/atom false) {:password-long-enough? long-enough?
same-password-length? #(and (seq @password) :password-validations validations
(= (count @password) (count @repeat-password)))] :password-strength (calc-password-strength validations)
(fn [] :empty-password? (empty? password)}))
(let [{user-color :color} (rf/sub [:onboarding/profile]) [password]))
{:keys [long-enough?]
:as validations} (password-validations @password)
password-strength (calc-password-strength validations)
empty-password? (empty? @password)
same-passwords? (and (not empty-password?) (= @password @repeat-password))
meet-requirements? (and (not empty-password?)
(utils.string/at-least-n-chars? @password 10)
same-passwords?
@accepts-disclaimer?)]
[:<>
[rn/view {:style style/top-part}
[header]
[password-inputs
{:password-long-enough? long-enough?
:passwords-match? same-passwords?
:empty-password? empty-password?
:show-password-validation? @show-password-validation?
:on-input-focus #(reset! focused-input :password)
:on-change-password (fn [new-value]
(reset! password new-value)
(when (same-password-length?)
(reset! show-password-validation? true)))
:on-change-repeat-password (fn [new-value]
(reset! repeat-password new-value)
(when (same-password-length?)
(reset! show-password-validation? true)))
:on-blur-repeat-password #(if empty-password?
(reset! show-password-validation? false)
(reset! show-password-validation? true))}]]
[rn/view {:style style/bottom-part} (defn- use-repeat-password-checks
(when same-passwords? [password repeat-password]
[rn/view {:style style/disclaimer-container} (rn/use-memo
[quo/disclaimer (fn []
{:blur? true {:same-password-length? (and (seq password) (= (count password) (count repeat-password)))
:on-change #(swap! accepts-disclaimer? not) :same-passwords? (and (seq password) (= password repeat-password))})
:checked? @accepts-disclaimer?} [password repeat-password]))
(i18n/label :t/password-creation-disclaimer)]])
(when (and (= @focused-input :password) (not same-passwords?))
[help
{:validations validations
:password-strength password-strength}])
[rn/view {:style style/button-container}
[quo/button
{:disabled? (not meet-requirements?)
:customization-color user-color
:on-press #(rf/dispatch
[:onboarding/password-set
(security/mask-data @password)])}
(i18n/label :t/password-creation-confirm)]]]]))))
(defn create-password-doc (defn create-password-doc
[] []
@ -183,38 +140,108 @@
[quo/text {:size :paragraph-2} [quo/text {:size :paragraph-2}
(i18n/label :t/create-profile-password-info-box-description)]]]) (i18n/label :t/create-profile-password-info-box-description)]]])
(defn- on-press-info
[]
(rn/dismiss-keyboard!)
(rf/dispatch [:show-bottom-sheet
{:content create-password-doc
:shell? true}]))
(defn- navigate-back
[]
(rf/dispatch [:navigate-back]))
(defn- page-nav
[]
(let [{:keys [top]} (safe-area/get-insets)]
[quo/page-nav
{:margin-top top
:background :blur
:icon-name :i/arrow-left
:on-press navigate-back
:right-side [{:icon-name :i/info
:on-press on-press-info}]}]))
(defn create-password (defn create-password
[] []
(reagent/with-let [keyboard-shown? (reagent/atom false) (let [[password set-password] (rn/use-state "")
{:keys [top bottom]} (safe-area/get-insets) [repeat-password set-repeat-password] (rn/use-state "")
will-show-listener (ocall rn/keyboard [accepts-disclaimer? set-accepts-disclaimer?] (rn/use-state false)
"addListener" [focused-input set-focused-input] (rn/use-state nil)
"keyboardWillShow" [show-password-validation?
#(reset! keyboard-shown? true)) set-show-password-validation?] (rn/use-state false)
will-hide-listener (ocall rn/keyboard {user-color :color} (rf/sub [:onboarding/profile])
"addListener"
"keyboardWillHide"
#(reset! keyboard-shown? false)) {:keys [password-long-enough?
on-press-info (fn [] password-validations password-strength
(rn/dismiss-keyboard!) empty-password?]} (use-password-checks password)
(rf/dispatch [:show-bottom-sheet
{:content create-password-doc {:keys [same-password-length? same-passwords?]} (use-repeat-password-checks password
:shell? true}]))] repeat-password)
[:<>
[rn/touchable-without-feedback meet-requirements? (rn/use-memo
{:on-press rn/dismiss-keyboard! #(and (not empty-password?)
:accessible false} (utils.string/at-least-n-chars? password
[rn/view {:style style/flex-fill} 10)
[rn/keyboard-avoiding-view {:style style/flex-fill} same-passwords?
[quo/page-nav accepts-disclaimer?)
{:margin-top top [password repeat-password
:background :blur accepts-disclaimer?])]
:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back]) [floating-button/view
:right-side [{:icon-name :i/info {:header [page-nav]
:on-press on-press-info}]}] :keyboard-should-persist-taps :handled
[password-form] :content-avoid-keyboard? true
[rn/view {:style {:height (if-not @keyboard-shown? bottom 0)}}]]]]] :blur-options
(finally {:blur-amount 34
(ocall will-show-listener "remove") :blur-radius 20
(ocall will-hide-listener "remove")))) :blur-type :transparent
:overlay-color :transparent
:background-color (if platform/android? colors/neutral-100 colors/neutral-80-opa-1-blur)
:padding-vertical 0
:padding-horizontal 0}
:footer-container-padding 0
:footer
[rn/view
{:style style/footer-container}
(when same-passwords?
[rn/view {:style style/disclaimer-container}
[quo/disclaimer
{:blur? true
:on-change (partial set-accepts-disclaimer? not)
:checked? accepts-disclaimer?}
(i18n/label :t/password-creation-disclaimer)]])
(when (and (= focused-input :password) (not same-passwords?))
[help
{:validations password-validations
:password-strength password-strength}])
[quo/button
{:container-style style/footer-button-container
:disabled? (not meet-requirements?)
:customization-color user-color
:on-press #(rf/dispatch
[:onboarding/password-set
(security/mask-data password)])}
(i18n/label :t/password-creation-confirm)]]}
[rn/view {:style style/form-container}
[header]
[password-inputs
{:password-long-enough? password-long-enough?
:passwords-match? same-passwords?
:empty-password? empty-password?
:show-password-validation? show-password-validation?
:on-input-focus #(set-focused-input :password)
:on-change-password (fn [new-value]
(set-password new-value)
(when same-password-length?
(set-show-password-validation? true)))
:on-change-repeat-password (fn [new-value]
(set-repeat-password new-value)
(when same-password-length?
(set-show-password-validation? true)))
:on-blur-repeat-password #(if empty-password?
(set-show-password-validation? false)
(set-show-password-validation? true))}]]]))