Compare commits

..
Author SHA1 Message Date
jo-mut e39c249868 Fix: logo and community image banner not showing
https://github.com/status-im/status-go/compare/4d705ce1...6b1cac69
2023-03-28 13:51:35 +03:00
J.M.N c6addce62a refactored scroll-page component to use flatlist instead of scroll view 2023-03-28 13:51:34 +03:00
J.M.N 18e2652ec3 mute community 2023-03-28 13:51:34 +03:00
J.M.N e938c2393a Fix: scroll-page component sticky header height 2023-03-28 13:51:34 +03:00
yqrashawn ac2d10bc5d fix: disable edit image message until it's implemented (#15496) 2023-03-28 17:04:01 +08:00
Ibrahem Khalil e825f930fa Add accessibility label for community options button (#15484) 2023-03-28 07:50:07 +02:00
frank 394dfde87b fixed #15446 (App crashes on syncing QR...) (#15464)
https://github.com/status-im/status-go/compare/4cc53630...458f2817
2023-03-28 12:27:22 +08:00
Alexander e4db23b0a9 Center input value within the field (#15472) 2023-03-27 23:00:33 +02:00
Ulises Manuel CárdenasandJamie Caprani 94ddbbcd2e Add checked? property, dark blur variant & tests to disclaimer component
Co-authored-by: Jamie Caprani <jamiecaprani@gmail.com>
2023-03-27 14:06:09 -06:00
Parvesh Monu ac27314547 fix login/forget password button overlaps password input (#15488) 2023-03-27 22:08:05 +05:30
Icaro Motta 7a4b12acf4 Make component test helpers usable from the REPL (#15468)
This commit makes the test-helpers.component namespace loadable in the REPL,
plus other changes that allow for a reasonably enjoyable RDD (REPL-Driven
Development) workflow.

Why? I want to be able to get instant feedback when I render a component with
the RN Testing Library (RNTL), and only once I'm satisfied with my findings is
when I proceed to write/update the tests. This nearly instant feedback loop is
only feasible using the ClojureScript REPL, and I'd rather not endure long
recompilation cycles.

Note that by REPL I mean connecting to the CLJS REPL of the Shadow-CLJS :mobile
target.

Essentially, this is what this commit does:

- [x] Allow the test-helpers.component namespace to be evaluated in the REPL.
      This is now possible because I changed all functions that assumed js/jest
      existed with a guard clause using the CLJS macro exists?. Without the
      guard clauses, evaluating the namespace explodes due to stuff like
      js/jest.useFakeTimers that fail in compile time (it's a syntax sugar
      macro).
- [x] Change the family of functions to get the translation by text to either
      translate using i18n/label or translate with the dummy prefix tx:,
      depending if the code is running inside the Jest runtime or not.
- [x] Wrap remaining RNTL query functions, except for the find-* ones, since
      they don't work at all outside the Jest runtime.
- [x] All wrapped functions support the original arguments supported by RNTL.
      Arguments are always converted with clj->js.
- [x] All wrapped functions can optionally take a node (ReactTestInstance) as
      their first argument, otherwise the global screen object will be used.
      This is very important! See the explanation on section Doesn't RNTL
      recommend using the screen object?
- [x] Update Shadow-CLJS preloads, so that (in development) you can fire off the
      REPL and always be ready to call component test helpers. This is critical!

What else would be possible? Just an idea, but now that we can easily render
components using the same machinery provided by RNTL in the tests, we can
roughly implement Storybook's Play function
https://storybook.js.org/docs/react/writing-stories/play-function

Lesson learned: In the REPL, you may need to call
(re-frame.core/clear-subscription-cache!), otherwise you will experience
subscriptions returning the same value if their arguments are the same. For
example, I faced this while playing with the namespace
status-im2.contexts.communities.menus.community-options.component-spec. There
are better ways to solve this particular problem in the context of tests if we
use the tooling provided by day8.re-frame.test.

Doesn't RNTL recommend using the screen object? Indeed, it is recommended to use
the screen object instead of destructuring the results of RNTL render. It's just
easier and less error prone, but this only works reliably within the Jest
runtime, since it automatically cleans up rendered state after each test. When
using the REPL this is no longer the case, and I faced some errors, like Unable
to find node on an unmounted component, where RNTL would refuse to re-render
components, even if I explicitly unmounted them or called cleanup.

The only reliable solution I found was to store the result of render (a node)
and pass it to every subsequent call. This is not a workaround, it's officially
supported, but it's a tad less convenient. You can also not pass the node
reference and it should work most of the time.

Practical examples

Workflow suggestion: write your local experiments in the same namespace as the
component spec and within the comment macro. This way, you can have the Jest
watcher running and a REPL connected to :mobile, and they won't step on each
other. For the test watcher, I usually change quo2-core-spec or
status-im2.core-spec to only require what I'm interested, otherwise Jest
consumes way too many resources.

```clojure
;; Namespace quo2.components.colors.color-picker.component-spec
(h/test "color picker color changed"
  (let [selected (reagent/atom nil)]
    (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))))

(comment
  (def selected (atom nil))
  (def c (h/render [color-picker/view {:on-change #(reset! selected %)}]))

  (h/fire-event :press (get (h/get-all-by-label-text c :color-picker-item) 0))

  ;; Options are passed down converted to JS types.
  (h/debug c {:message "Rendering header"})

  @selected ; => :blue
)
```

```clojure
;; Namespace quo2.components.tags.--tests--.status-tags-component-spec
(h/test "renders status tag with pending type"
  (render-status-tag {:status {:type :pending}
                      :label  "Pending"
                      :size   :small})
  (-> (h/expect (h/get-all-by-label-text :status-tag-pending))
      (.toBeTruthy))
  (-> (h/expect (h/get-by-text "Pending"))
      (.toBeTruthy)))

(comment
  (def c (render-status-tag {:status {:type :pending}
                             :label  "Pending"
                             :size   :small}))

  (h/get-all-by-label-text c :status-tag-pending))
```

```clojure
;; Namespace status-im2.contexts.communities.menus.community-options.component-spec
(h/test "joined and muted community"
  (setup-subs {:communities/my-pending-request-to-join nil
               :communities/community                  {:joined       true
                                                        :muted        true
                                                        :token-gated? true}})
  (h/render [options/community-options-bottom-sheet {:id "test"}])
  (-> (h/expect (h/get-by-translation-text :unmute-community))
      (.toBeTruthy)))

(comment
  (setup-subs {:communities/my-pending-request-to-join nil
               :communities/community                  {:joined       true
                                                        :muted        true
                                                        :token-gated? true}})
  (def c (h/render [options/community-options-bottom-sheet {:id "test"}]))
  (some? (h/get-by-translation-text c :invite-people-from-contacts)) ; => true
)
```
2023-03-27 11:54:56 -03:00
Brian Sztamfater 4e6dea6b36 feat: enable biometrics screen 2023-03-27 14:23:23 +01:00
29 changed files with 500 additions and 213 deletions
+29
View File
@@ -61,6 +61,35 @@ the source file. For a real example, see
[rn/view (do-something)]])
```
### Always add styles inside the `:style` key
Although when compiling ReactNative for mobile some components are able work with
their styles in the top-level of the properties map, prefer to add them inside the
`:style` key in order to separate styles from properties:
```clojure
;; bad
[rn/button {:flex 1
:padding-vertical 10
:padding-horizontal 20
:on-press #(js/alert "Hi!")
:title "Button"}]
;; good
[rn/button {:style {:flex 1
:padding-vertical 10
:padding-horizontal 20}
:on-press #(js/alert "Hi!")
:title "Button"}]
;; better
;; (define them in a style ns & place them inside `:style` key)
[rn/button {:style (style/button)
:on-press #(js/alert "Hi!")
:title "Button"}
]
```
### Don't use percents to define width/height
In ReactNative, all layouts use the [flexbox
+7 -1
View File
@@ -45,7 +45,13 @@
:devtools {:autobuild #shadow/env ["SHADOW_AUTOBUILD_ENABLED" :default true :as :bool]}
:dev {:devtools {:after-load status-im2.setup.hot-reload/reload
:build-notify status-im2.setup.hot-reload/build-notify
:preloads [re-frisk-remote.preload]}
:preloads [re-frisk-remote.preload
;; In order to use component test helpers in
;; the REPL we need to preload namespaces
;; that are not normally required by
;; production code, such as
;; @testing-library/react-native.
test-helpers.component]}
:closure-defines
{status-im2.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
status-im2.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"}
@@ -16,15 +16,13 @@
:height 230
:border-radius 20}
:on-press on-press}
[rn/view
{:flex 1}
[rn/view {:flex 1}
[rn/view (style/community-cover-container 60)
[rn/image
{:source cover
:style
{:flex 1
:border-top-right-radius 20
:border-top-left-radius 20}}]]
:style {:flex 1
:border-top-right-radius 20
:border-top-left-radius 20}}]]
[rn/view (style/card-view-content-container 12)
[rn/view (style/card-view-chat-icon 48)
[icon/community-icon {:images images} 48]]
@@ -38,7 +36,8 @@
{:title name
:description description}]
[rn/view {:style (style/card-stats-position)}
[community-view/community-stats-column :card-view]]
[community-view/community-stats-column
{:type :card-view}]]
[rn/view {:style (style/community-tags-position)}
[community-view/community-tags tags]]]]]]])
@@ -64,7 +64,8 @@
colors/neutral-40
colors/neutral-60))}}
name]
[community-view/community-stats-column :list-view]]
[community-view/community-stats-column
{:type :list-view}]]
(if (= status :gated)
[community-view/permission-tag-container
{:locked? locked?
@@ -23,7 +23,7 @@
members-count]])
(defn community-stats-column
[type]
[{:keys [type]}]
(let [icon-color (colors/theme-colors colors/neutral-50 colors/neutral-40)]
[rn/view
(if (= type :card-view)
@@ -46,11 +46,12 @@
^{:key name}
[rn/view {:margin-right 8}
[tag/tag
{:size 24
:label name
:type :emoji
:labelled? true
:resource emoji}]])])
{:size 24
:label name
:type :emoji
:labelled? true
:scrollable? true
:resource emoji}]])])
(defn community-title
[{:keys [title description size] :or {size :small}}]
+1 -1
View File
@@ -62,7 +62,7 @@
:bottom 0
:left 0
:right 0
:height 20
:border-radius 20
:padding-horizontal padding-horizontal
:border-top-right-radius 16
:border-top-left-radius 16
+1 -1
View File
@@ -91,7 +91,7 @@
:color (:text colors-by-status))]
(if multiple-lines?
(assoc base-props :text-align-vertical :top)
(assoc base-props :height (if small? 30 38)))))
(assoc base-props :height (if small? 30 38) :line-height nil))))
(defn right-icon-touchable-area
[small?]
+6 -4
View File
@@ -150,13 +150,15 @@
:justify-content :flex-end)}
(let [last-icon-index (-> right-section-buttons count dec)]
(map-indexed (fn [index
{:keys [icon on-press type style icon-override-theme]
{:keys [icon on-press type style icon-override-theme accessibility-label]
:or {type :grey}}]
^{:key index}
[rn/view
{:style (assoc style
:margin-right
(if (= index last-icon-index) 0 8))}
(cond-> {:style (assoc style
:margin-right
(if (= index last-icon-index) 0 8))}
accessibility-label (assoc :accessibility-label accessibility-label
:accessible true))
[button/button
{:on-press on-press
:icon true
@@ -0,0 +1,29 @@
(ns quo2.components.selectors.disclaimer.component-spec
(:require [quo2.components.selectors.disclaimer.view :as disclaimer]
[test-helpers.component :as h]))
(h/describe "Disclaimer tests"
(h/test "Default render of toggle component"
(h/render [disclaimer/view {:on-change (h/mock-fn)} "test"])
(h/is-truthy (h/get-by-label-text :checkbox-off)))
(h/test "Renders its text"
(let [text "I accept this disclaimer"]
(h/render [disclaimer/view {} text])
(h/is-truthy (h/get-by-text text))))
(h/test "On change event gets fire after press"
(let [mock-fn (h/mock-fn)]
(h/render [disclaimer/view {:on-change mock-fn} "test"])
(h/fire-event :press (h/get-by-label-text :checkbox-off))
(h/was-called mock-fn)))
(h/describe "It's rendered according to its `checked?` property"
(h/test "checked? true"
(h/render [disclaimer/view {:checked? true} "test"])
(h/is-null (h/query-by-label-text :checkbox-off))
(h/is-truthy (h/query-by-label-text :checkbox-on)))
(h/test "checked? false"
(h/render [disclaimer/view {:checked? false} "test"])
(h/is-null (h/query-by-label-text :checkbox-on))
(h/is-truthy (h/query-by-label-text :checkbox-off)))))
@@ -2,14 +2,16 @@
(:require [quo2.foundations.colors :as colors]))
(defn container
[]
{:flex-direction :row
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-80-opa-40)
:padding 11
:align-self :stretch
:border-radius 12
:border-width 1
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-70)})
[blur?]
(let [dark-background (if blur? colors/white-opa-5 colors/neutral-80-opa-40)
dark-border (if blur? colors/white-opa-10 colors/neutral-70)]
{:flex-direction :row
:background-color (colors/theme-colors colors/neutral-5 dark-background)
:padding 11
:align-self :stretch
:border-radius 12
:border-width 1
:border-color (colors/theme-colors colors/neutral-20 dark-border)}))
(def text
{:margin-left 8})
@@ -5,9 +5,9 @@
[react-native.core :as rn]))
(defn view
[{:keys [checked? on-change accessibility-label container-style]} label]
[{:keys [checked? blur? on-change accessibility-label container-style]} label]
[rn/view
{:style (merge container-style (style/container))}
{:style (merge container-style (style/container blur?))}
[selectors/checkbox
{:accessibility-label accessibility-label
:on-change on-change
+1
View File
@@ -19,5 +19,6 @@
[quo2.components.record-audio.record-audio.--tests--.record-audio-component-spec]
[quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec]
[quo2.components.selectors.--tests--.selectors-component-spec]
[quo2.components.selectors.disclaimer.component-spec]
[quo2.components.selectors.filter.component-spec]
[quo2.components.tags.--tests--.status-tags-component-spec]))
+8 -4
View File
@@ -225,8 +225,12 @@
(defn format-members
[count]
(if (> count 1000000)
(cond
(> count 1000000)
(str (with-precision (/ count 1000000) 1) (i18n/label :t/M))
(if (and (> count 999) (< count 1000000))
(str (with-precision (/ count 1000) 1) (i18n/label :t/K))
count)))
(< 999 count 1000000)
(str (with-precision (/ count 1000) 1) (i18n/label :t/K))
:else
count))
+25 -21
View File
@@ -89,7 +89,7 @@
(defn display-picture
[scroll-height cover]
[scroll-height logo]
(let [input-range (if platform/ios? [-67 10] [0 150])
y (reanimated/use-shared-value scroll-height)
animation (reanimated/interpolate y
@@ -104,7 +104,7 @@
[reanimated/view
{:style (style/display-picture-container animation)}
[rn/image
{:source cover
{:source logo
:style style/display-picture}]]))
(defn scroll-page
@@ -117,7 +117,7 @@
[:<>
[:f> scroll-page-header @scroll-height height name page-nav-right-section-buttons
logo sticky-header top-nav title-colum navigate-back?]
[rn/scroll-view
[rn/flat-list
{:content-container-style (style/scroll-view-container
(diff-with-max-min @scroll-height 16 0))
:shows-vertical-scroll-indicator false
@@ -128,21 +128,25 @@
event
"nativeEvent.contentOffset.y")))
(when on-scroll
(on-scroll @scroll-height)))}
(when cover-image
[rn/view {:style {:height 151}}
[rn/image
{:source cover-image
;; Using negative margin-bottom as a workaround because on Android,
;; ScrollView clips its children despite setting overflow: 'visible'.
;; Related issue: https://github.com/facebook/react-native/issues/31218
:style {:margin-bottom -16
:flex 1}}]])
(when children
[rn/view
{:flex 1
:border-radius (diff-with-max-min @scroll-height 16 0)
:background-color background-color}
(when cover-image
[:f> display-picture @scroll-height logo])
children])]])))
(on-scroll @scroll-height)))
:header [rn/view
(when cover-image
[rn/view {:style {:height 151}}
[rn/image
{:source cover-image
;; Using negative margin-bottom as a workaround because
;; on Android,
;; ScrollView clips its children despite setting
;; overflow: 'visible'.
;; Related issue:
;; https://github.com/facebook/react-native/issues/31218
:style {:margin-bottom -16
:flex 1}}]])
(when children
[rn/view
{:flex 1
:border-radius (diff-with-max-min @scroll-height 16 0)
:background-color background-color}
(when cover-image
[:f> display-picture @scroll-height logo])
children])]}]])))
+5
View File
@@ -318,3 +318,8 @@
(def ^:const empty-category-id :communities/not-categorized)
(def ^:const seed-phrase-valid-length #{12 18 24})
(def ^:const auth-method-password "password")
(def ^:const auth-method-biometric "biometric")
(def ^:const auth-method-biometric-prepare "biometric-prepare")
(def ^:const auth-method-none "none")
@@ -27,6 +27,10 @@
(concat
(when (and outgoing
(not (or deleted? deleted-for-me?))
;; temporarily disable edit image message until
;; https://github.com/status-im/status-mobile/issues/15298
;; is implemented
(not= content-type constants/content-type-image)
(not= content-type constants/content-type-audio))
[{:type :main
:on-press #(rf/dispatch [:chat.ui/edit-message message-data])
@@ -22,10 +22,11 @@
(def featured-list-container
{:flex-direction :row
:overflow :hidden
:margin-bottom 24
:margin-left 20
:padding-right 20})
:overflow :hidden})
(def flat-list-container
{:padding-bottom 24
:padding-horizontal 20})
(def other-communities-container
{:flex 1
@@ -7,7 +7,6 @@
[reagent.core :as reagent]
[status-im2.common.resources :as resources]
[status-im2.contexts.communities.menus.community-options.view :as options]
[status-im.ui.screens.communities.community :as community]
[status-im.ui.components.react :as react]
[react-native.platform :as platform]
[status-im2.common.scroll-page.view :as scroll-page]
@@ -22,23 +21,28 @@
:group [{:id 1
:token-icon (resources/get-mock-image :status-logo)}]}]}})
(defn render-fn
[community-item _ _ {:keys [width view-type]}]
(let [item (merge community-item
(get mock-community-item-data :data))
cover {:uri (get-in (:images item) [:banner :uri])}]
(defn community-list-item
[{:keys [id] :as community} _ _ {:keys [width view-type]}]
(let [community-item (merge
community
(get mock-community-item-data :data))
cover {:uri (get-in (:images community) [:banner :uri])}]
(if (= view-type :card-view)
[quo/community-card-view-item (assoc item :width width :cover cover)
#(rf/dispatch [:navigate-to :community-overview (:id item)])]
[quo/community-card-view-item (assoc community-item :width width :cover cover)
#(rf/dispatch [:navigate-to :community-overview (:id community)])]
[quo/communities-list-view-item
{:on-press (fn []
(rf/dispatch [:communities/load-category-states (:id item)])
(rf/dispatch [:communities/load-category-states id])
(rf/dispatch [:dismiss-keyboard])
<<<<<<< HEAD
(rf/dispatch [:navigate-to :community-overview (:id item)]))
=======
(rf/dispatch [:navigate-to :community {:community-id id}]))
>>>>>>> 1675fef63 (Fix: community data not displayed)
:on-long-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:content (fn []
[options/community-options-bottom-sheet (:id item)])}])}])))
[options/community-options-bottom-sheet id])}])}])))
(defn screen-title
[]
@@ -89,7 +93,6 @@
:label (i18n/label :t/gated)
:accessibility-label :gated-communities-tab}]}]])
(defn featured-list
[communities view-type]
(let [view-size (reagent/atom 0)]
@@ -98,7 +101,7 @@
{:style style/featured-list-container
:on-layout #(swap! view-size
(fn []
(- (oops/oget % "nativeEvent.layout.width") 20)))}
(- (oops/oget % "nativeEvent.layout.width") 40)))}
(when-not (= @view-size 0)
[rn/flat-list
{:key-fn :id
@@ -107,9 +110,22 @@
:shows-horizontal-scroll-indicator false
:separator [rn/view {:width 12}]
:data communities
:render-fn render-fn
:render-fn community-list-item
:render-data {:width @view-size
:view-type view-type}}])])))
:view-type view-type}
:contentContainerStyle style/flat-list-container}])])))
(defn other-communities-list
[{:keys [communities view-type]}]
[rn/view style/other-communities-container
[rn/flat-list
{:key-fn :id
:keyboard-should-persist-taps :always
:separator [rn/view {:height 16}]
:data communities
:render-fn community-list-item
:contentContainerStyle style/flat-list-container
:render-data {:view-type view-type}}]])
(defn discover-communities-header
[{:keys [featured-communities-count
@@ -120,39 +136,10 @@
[screen-title]
[featured-communities-header featured-communities-count]
[featured-list featured-communities view-type]
[quo/separator]
[rn/view {:style {:margin-horizontal 20}}
[quo/separator]]
[discover-communities-segments selected-tab false]])
(defn other-communities-list
[{:keys [communities communities-ids view-type]}]
[rn/view {:style style/other-communities-container}
(map-indexed
(fn [inner-index item]
(let [community-id (when communities-ids item)
community (if communities
item
[rf/sub [:communities/home-item community-id]])]
[rn/view
{:key (str inner-index (:id community))
:margin-bottom 16}
(if (= view-type :card-view)
[quo/community-card-view-item
(merge community
(get mock-community-item-data :data))
#(rf/dispatch [:navigate-to :community-overview (:id community)])]
[quo/communities-list-view-item
{:on-press (fn []
(rf/dispatch [:communities/load-category-states (:id community)])
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:navigate-to :community-overview (:id community)]))
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn []
;; TODO implement with quo2
[community/community-actions community])}])}
(merge community
(get mock-community-item-data :data))])]))
(if communities communities communities-ids))])
(defn communities-lists
[selected-tab view-type]
[rn/view {:style {:flex 1}}
@@ -295,14 +295,14 @@
(defn page-nav-right-section-buttons
[id]
[{:icon :i/options
:background-color (scroll-page/icon-color)
:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:content
(fn []
[options/community-options-bottom-sheet
id])}])}])
[{:icon :i/options
:background-color (scroll-page/icon-color)
:accessibility-label :community-options-for-community
:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:content (fn []
[options/community-options-bottom-sheet
id])}])}])
(defn pick-first-category-by-height
[scroll-height first-channel-height categories-heights]
@@ -12,3 +12,12 @@
:background-color colors/neutral-80-opa-80-blur})
(def navigation-bar {:height 56})
(def image-container
{:margin-top 20
:margin-bottom 24
:background-color colors/danger-50
:border-radius 20
:flex 1
:align-items :center
:justify-content :center})
@@ -5,7 +5,8 @@
[status-im2.contexts.onboarding.enable-biometrics.style :as style]
[utils.i18n :as i18n]
[status-im2.contexts.onboarding.common.background.view :as background]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[status-im.multiaccounts.biometric.core :as biometric]))
(defn navigation-bar
[]
@@ -17,18 +18,39 @@
(defn page
[]
[rn/view {:style style/page-container}
[navigation-bar]
[rn/view {:style {:padding-horizontal 20}}
[quo/text
{:size :heading-1
:weight :semi-bold
:style {:color colors/white}} "Enable-biometrics"]
[quo/button
{:on-press #(rf/dispatch [:onboarding-2/create-account-and-login])
:type :grey
:override-theme :dark
:style {}} (i18n/label :t/continue)]]])
(let [supported-biometric (rf/sub [:supported-biometric-auth])
bio-type-label (biometric/get-label supported-biometric)
profile-color (:color (rf/sub [:onboarding-2/profile]))]
[rn/view {:style style/page-container}
[navigation-bar]
[rn/view
{:style {:padding-horizontal 20
:flex 1}}
[quo/text
{:size :heading-1
:weight :semi-bold
:style {:color colors/white}} (i18n/label :t/enable-biometrics)]
[quo/text
{:size :paragraph-1
:style {:color colors/white
:margin-top 8}}
(i18n/label :t/use-biometrics)]
;; TODO(@briansztamfater): Replace view with image view with the real illustration,
;; https://github.com/status-im/status-mobile/issues/15445
[rn/view {:style style/image-container}
[quo/text {:size :paragraph-1}
"Illustration here"]]
[rn/view {:style {:margin-bottom 55}}
[quo/button
{:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])
:before :i/face-id
:override-background-color (colors/custom-color profile-color 50)}
(i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]
[quo/button
{:on-press #(rf/dispatch [:onboarding-2/create-account-and-login])
:override-background-color colors/white-opa-5
:style {:margin-top 12}}
(i18n/label :t/maybe-later)]]]]))
(defn enable-biometrics
[]
+29 -2
View File
@@ -7,7 +7,9 @@
[clojure.string :as string]
[utils.security.core :as security]
[status-im.native-module.core :as status]
[status-im.ethereum.core :as ethereum]))
[status-im.ethereum.core :as ethereum]
[status-im2.constants :as constants]
[utils.i18n :as i18n]))
(re-frame/reg-fx
:multiaccount/create-account-and-login
@@ -36,6 +38,29 @@
{:db (update db :onboarding-2/profile merge onboarding-data)
:dispatch [:navigate-to :create-profile-password]})
(rf/defn enable-biometrics
{:events [:onboarding-2/enable-biometrics]}
[_]
{:biometric-auth/authenticate [#(rf/dispatch [:onboarding-2/biometrics-done %]) {}]})
(rf/defn show-biometrics-message
[cofx bioauth-message bioauth-code]
(let [content (or (when (get #{"NOT_AVAILABLE" "NOT_ENROLLED"} bioauth-code)
(i18n/label :t/grant-face-id-permissions))
bioauth-message)]
(when content
{:utils/show-popup
{:title (i18n/label :t/biometric-auth-login-error-title)
:content content}})))
(rf/defn biometrics-done
{:events [:onboarding-2/biometrics-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
(if bioauth-success
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
:dispatch [:onboarding-2/create-account-and-login]}
(show-biometrics-message cofx bioauth-message bioauth-code)))
(defn strip-file-prefix
[path]
(when path
@@ -87,7 +112,9 @@
(rf/defn password-set
{:events [:onboarding-2/password-set]}
[{:keys [db]} password]
{:db (assoc-in db [:onboarding-2/profile :password] password)
{:db (-> db
(assoc-in [:onboarding-2/profile :password] password)
(assoc-in [:onboarding-2/profile :auth-method] constants/auth-method-password))
:dispatch [:navigate-to :enable-biometrics]})
(rf/defn seed-phrase-entered
@@ -42,19 +42,19 @@
:padding-horizontal 20})
(def multi-profile-button
{:align-self :flex-end})
{:align-self :flex-end
:margin-bottom 20})
(def login-profile-card
{:margin-vertical 20})
(def keyboard-avoiding-view
{:flex 1})
{:margin-bottom 20})
(def info-message
{:margin-top 8})
(def forget-password-button
{:margin-vertical 8})
(defn login-button
[]
{:margin-top 8
:margin-bottom (if platform/android? 20 46)})
{:margin-bottom (if platform/android? 20 46)})
@@ -135,18 +135,19 @@
profile-picture (:uri (first (:images multiaccount)))]
[rn/keyboard-avoiding-view
{:style style/login-container}
[rn/view
{:style {:flex 1}}
[quo/button
{:size 32
:type :blur-bg
:icon true
:on-press #(reset! show-profiles? true)
:override-theme :dark
:width 32
:accessibility-label :show-profiles
:style style/multi-profile-button}
:i/multi-profile]
[quo/button
{:size 32
:type :blur-bg
:icon true
:on-press #(reset! show-profiles? true)
:override-theme :dark
:width 32
:accessibility-label :show-profiles
:style style/multi-profile-button}
:i/multi-profile]
[rn/scroll-view
{:keyboard-should-persist-taps :always
:style {:flex 1}}
[quo/profile-card
{:name name
:customization-color (or customization-color :primary)
@@ -178,7 +179,8 @@
:type :ghost
:before :i/info
:accessibility-label :forget-password-button
:override-theme :dark}
:override-theme :dark
:style style/forget-password-button}
(i18n/label :t/forget-password)]
[quo/button
{:size 40
@@ -1,28 +1,56 @@
(ns status-im2.contexts.quo-preview.selectors.disclaimer
(:require [quo2.components.buttons.button :as button]
[quo2.components.selectors.disclaimer.view :as quo]
[quo2.components.selectors.disclaimer.view :as disclaimer]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[reagent.core :as reagent]
[status-im2.contexts.quo-preview.preview :as preview]))
(def descriptor
[{:label "Checked:"
:key :checked?
:type :boolean}
{:label "Blur (only for dark theme):"
:key :blur?
:type :boolean}
{:label "Text"
:key :text
:type :text}])
(defn blur-background
[blur?]
(when (and blur? (theme/dark?))
[rn/view
{:style {:position :absolute
:top 0
:bottom 0
:left 0
:right 0}}
[preview/blur-view
{:style {:flex 1}
:show-blur-background? true}]]))
(defn cool-preview
[]
(let [checked? (reagent/atom false)]
(let [state (reagent/atom {:checked? false
:blur? true
:text "I agree with the community rules"})]
(fn []
[rn/view
{:margin-bottom 50
:padding-vertical 16
:padding-horizontal 20}
[rn/view
{:padding-vertical 60
:align-items :center}
[quo/view
{:container-style {:margin-bottom 40}
:on-change #(swap! checked? not)}
"I agree with the community rules"]
[button/button
{:disabled (not @checked?)}
"submit"]]])))
(let [{:keys [blur? checked? text]} @state]
[rn/view {:style {:flex 1}}
[rn/view {:style {:flex 1}}
[preview/customizer state descriptor]]
[rn/view {:style {:padding-horizontal 15}}
[blur-background blur?]
[rn/view {:style {:margin-vertical 50}}
[disclaimer/view
{:blur? blur?
:checked? checked?
:on-change #(swap! state update :checked? not)}
text]]
[button/button {:disabled (not checked?)}
"submit"]]]))))
(defn preview-disclaimer
[]
+17 -7
View File
@@ -151,7 +151,9 @@
{:name :create-profile-password
:options {:statusBar {:style :light}
:topBar {:visible false}
:topBar {:visible false
:backButton {:popStackOnPress false}}
:navigationBar {:backgroundColor colors/black}}
:insets {:top false}
:component create-password/create-password}
@@ -164,9 +166,13 @@
:component enable-biometrics/enable-biometrics}
{:name :generating-keys
:options {:statusBar {:style :light}
:topBar {:visible false}
:navigationBar {:backgroundColor colors/black}}
:options {:statusBar {:style :light}
:navigationBar {:backgroundColor colors/black}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}
:topBar {:visible false
:backButton {:popStackOnPress false}}}
:insets {:top false}
:component generating-keys/generating-keys}
@@ -178,9 +184,13 @@
:component enter-seed-phrase/enter-seed-phrase}
{:name :enable-notifications
:options {:statusBar {:style :light}
:topBar {:visible false}
:navigationBar {:backgroundColor colors/black}}
:options {:statusBar {:style :light}
:navigationBar {:backgroundColor colors/black}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}
:topBar {:visible false
:backButton {:popStackOnPress false}}}
:insets {:top false}
:component enable-notifications/enable-notifications}
+149 -37
View File
@@ -1,69 +1,181 @@
(ns test-helpers.component
"Helpers for writing component tests using React Native Testing Library."
(:require-macros test-helpers.component)
(:require ["@testing-library/react-native" :as rtl]
[camel-snake-kebab.core :as camel-snake-kebab]
[reagent.core :as reagent]))
(:require
["@testing-library/react-native" :as rtl]
[camel-snake-kebab.core :as camel-snake-kebab]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
;;;; React Native Testing Library
(defn- with-node-or-screen
"Wrap RN Testing Library `method-name` and call it either on a ReactTestInstance
or directly on the screen object.
`method-name` can be either the name of the native method, or a kebab case
keyword.
It is often necessary in REPL sessions to call methods on the returned
instance of RNTL `render` method, otherwise you can get weird errors, like
'Unable to find node on an unmounted component'. This happens because RNTL was
mainly conceptualized to run inside a test runner that automatically cleans up
everything after each test.
Usage:
(def get-by-text (wrap-screen-or-node :get-by-text))
In another file, and with the REPL running with the Shadow-CLJS `:mobile`
target:
(comment
;; Consider using a shorter var name when playing in a REPL.
(def component (h/render [quo/counter {} 50]))
(h/get-by-text component \"50\")
;; Or without the node it works too, but it is only reliable inside
;; a test runner.
(h/get-by-text \"50\"))
"
[method-name]
(let [method-name (camel-snake-kebab/->camelCaseString method-name)]
(fn [& args]
(if (= js/Object (type (first args))) ; Check if it's a node instance.
(let [method (aget (first args) method-name)]
(apply method (clj->js (rest args))))
(let [method (aget rtl/screen method-name)]
(apply method (clj->js args)))))))
(defn render
[component]
(rtl/render (reagent/as-element component)))
(def unmount
"Unmount rendered component.
Sometimes useful to be called in a REPL, but unnecessary when rendering
components with Jest, since components are automatically unmounted after each
test."
(with-node-or-screen :unmount))
(def debug
"Pretty-print to STDOUT the current component tree."
(with-node-or-screen :debug))
(defn fire-event
([event-name element]
(fire-event event-name element nil))
([event-name element data]
([event-name node]
(fire-event event-name node nil))
([event-name node data]
(rtl/fireEvent
element
node
(camel-snake-kebab/->camelCaseString event-name)
(clj->js data))))
(defn debug
[element]
(rtl/screen.debug element))
;;; Queries: find-*
;;
;; find-* functions don't work in the REPL because the returned promise is
;; always rejected with ReferenceError: Can't find variable: MessageChannel
;;
;; For this reason, find-* functions only work within the Jest runtime, hence
;; using the wrapper function `with-node-or-screen` is unnecessary.
(defn get-by-test-id
[test-id]
(rtl/screen.getByTestId (name test-id)))
(def find-by-text (comp rtl/screen.findByText name))
(defn get-by-text
[text]
(rtl/screen.getByText text))
;;; Queries that work with a REPL and with Jest
(defn find-by-text
[text]
(rtl/screen.findByText text))
(def get-all-by-text (with-node-or-screen :get-all-by-text))
(def get-by-text (with-node-or-screen :get-by-text))
(def query-all-by-text (with-node-or-screen :query-all-by-text))
(def query-by-text (with-node-or-screen :query-by-text))
(defn get-by-label-text
[label]
(rtl/screen.getByLabelText (name label)))
(def get-all-by-label-text (with-node-or-screen :get-all-by-label-text))
(def get-by-label-text (with-node-or-screen :get-by-label-text))
(def query-all-by-label-text (with-node-or-screen :query-all-by-label-text))
(def query-by-label-text (with-node-or-screen :query-by-label-text))
(defn query-by-label-text
"Returns `nil` when label is not found."
[label]
(rtl/screen.queryByLabelText (name label)))
(def get-all-by-display-value (with-node-or-screen :get-all-by-display-value))
(def get-by-display-value (with-node-or-screen :get-by-display-value))
(def query-all-by-display-value (with-node-or-screen :query-all-by-display-value))
(def query-by-display-value (with-node-or-screen :query-by-display-value))
(def get-all-by-placeholder-text (with-node-or-screen :get-all-by-placeholder-text))
(def get-by-placeholder-text (with-node-or-screen :get-by-placeholder-text))
(def query-all-by-placeholder-text (with-node-or-screen :query-all-by-placeholder-text))
(def query-by-placeholder-text (with-node-or-screen :query-by-placeholder-text))
(def get-all-by-role (with-node-or-screen :get-all-by-role))
(def get-by-role (with-node-or-screen :get-by-role))
(def query-all-by-role (with-node-or-screen :query-all-by-role))
(def query-by-role (with-node-or-screen :query-by-role))
(def get-all-by-test-id (with-node-or-screen :get-all-by-test-id))
(def get-by-test-id (with-node-or-screen :get-by-test-id))
(def query-all-by-test-id (with-node-or-screen :query-all-by-test-id))
(def query-by-test-id (with-node-or-screen :query-by-test-id))
(defn- prepare-translation
[translation]
(if (exists? js/jest)
;; Translations are treated differently when running with Jest. See
;; test/jest/jestSetup.js for more details.
(str "tx:" (name translation))
(i18n/label translation)))
(defn get-all-by-translation-text
([translation]
(get-all-by-translation-text rtl/screen translation))
([^js node translation & args]
(apply (with-node-or-screen :get-all-by-text) node (prepare-translation translation) args)))
(defn get-by-translation-text
[keyword]
(get-by-text (str "tx:" (name keyword))))
([translation]
(get-by-translation-text rtl/screen translation))
([^js node translation & args]
(apply (with-node-or-screen :get-by-text) node (prepare-translation translation) args)))
(defn get-all-by-label-text
[label]
(rtl/screen.getAllByLabelText (name label)))
(defn query-by-translation-text
([translation]
(query-by-translation-text rtl/screen translation))
([^js node translation & args]
(apply (with-node-or-screen :query-by-text) node (prepare-translation translation) args)))
(defn expect [match] (js/expect match))
(defn query-all-by-translation-text
([translation]
(query-all-by-translation-text rtl/screen translation))
([^js node translation & args]
(apply (with-node-or-screen :query-all-by-text) node (prepare-translation translation) args)))
(defn use-fake-timers [] (js/jest.useFakeTimers))
;;; Jest utilities
(defn clear-all-timers [] (js/jest.clearAllTimers))
(def ^:private jest?
(exists? js/jest))
(defn use-real-timers [] (js/jest.useRealTimers))
(defn expect
[match]
(js/expect match))
(defn use-fake-timers
[]
(when jest?
(js/jest.useFakeTimers)))
(defn clear-all-timers
[]
(when jest?
(js/jest.clearAllTimers)))
(defn use-real-timers
[]
(when jest?
(js/jest.useRealTimers)))
(defn advance-timers-by-time
[time-ms]
(js/jest.advanceTimersByTime time-ms))
(when jest?
(js/jest.advanceTimersByTime time-ms)))
(def mock-fn js/jest.fn)
(def mock-fn
(when jest?
js/jest.fn))
(defn is-truthy
[element]
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.140.2",
"commit-sha1": "bcca0b399d8b8b3f0ef438c4ebc39f4f4359da26",
"src-sha256": "1m625v69x4mzh5gnvxnf0i6xxyxhwc1ic39f5xf72ybaika1arzn"
"version": "v0.141.1",
"commit-sha1": "458f28178ca15732df8e6c806ef5326fad44427c",
"src-sha256": "1hhfswn17dja62rxkb572afd6fsx86wfxs4j666lhscdz7zzs4gx"
}
+3 -1
View File
@@ -2052,5 +2052,7 @@
"ensure-qr-code-is-in-focus-to-scan":"Ensure that the QR code is in focus to scan",
"error-this-is-not-a-sync-qr-code": "Oops! This is not a sync QR code",
"error-syncing-connection-failed": "Oops! Connection failed. Try again",
"camera-permission-denied": "Permission denied"
"camera-permission-denied": "Permission denied",
"enable-biometrics": "Enable biometrics",
"use-biometrics": "Use biometrics to fill in your password"
}