Compare commits

..
Author SHA1 Message Date
Volodymyr Kozieiev 6558dfc25a Add possibility to configure network icon in data-item component 2023-11-22 15:48:30 +00:00
Omar Basem 73e9f67dbd Wallet: illustrations (#17931)
* wallet: illustrations
2023-11-21 10:55:46 +04:00
Jamie Caprani 86c5505c94 chore(wallet): hook up qr scanner on watch only flow. (#17829) 2023-11-19 16:26:00 -08:00
Icaro Motta c1dcd7a764 Introduce malli library (#17867)
This commit is the foundational step to start using malli
(https://github.com/metosin/malli) in this project.

Take in consideration we will only be able to realize malli's full power in
future iterations.

For those without context: the mobile team watched a presentation about malli
and went through a light RFC to put everyone on the same page, among other
discussions here and there in PRs.

To keep things relatively short:

1. Unit, integration and component tests will short-circuit (fail) when
   inputs/outputs don't conform to their respective function schemas (CI should
   fail too).
2. Failed schema checks will not block the app from initializing, nor throw an
   exception that would trigger the LogBox. Exceptions are only thrown in the
   scope of automated tests.
3. There's zero performance impact in production code because we only
   instrument. Instrumentation is removed from the compiled code due to the
   usage of "^boolean js.goog/DEBUG".
4. We shouldn't expect any meaningful slowdown during development.

**What are we instrumenting in this PR?**

Per our team's agreement, we're only instrumenting the bare minimum to showcase 2 examples.

- Instrument a utility function utils.money/format-amount using the macro
  approach.
- Instrument a quo component quo.components.counter.step.view/view using the
  functional approach.

Both approaches are useful, the functional approach is powerful and allow us to
instrument anonymous functions, like the ones we pass to subscriptions or event
handlers, or the higher-order function quo.theme/with-theme. The macro approach
is perfect for functions already defined with defn.

**I evaluated the schema or function in the REPL but nothing changes**

- If you evaluate the source function, you need to evaluate schema/=> or
  schema/instrument as well.
- Remember to *var quote* when using schema/instrument.
- You must call "(status-im2.setup.schema/setup!)" after any var is
  re-instrumented. It's advisable to add a keybinding in your editor to send
  this expression automatically to the CLJS REPL, or add the call at the end of
  the namespace you are working on (similar to how some devs add "(run-tests)"
  at the end of test namespaces).

**Where should schemas be defined?**

For the moment, we should focus on instrumenting quo components, so define each
function schema in the same namespace as the component's public "view" var.

To be specific:

- A schema used only to instrument a single function and not used elsewhere,
  like a quo component schema, wouldn't benefit from being defined in a separate
  namespace because that would force the developer to constantly open two files
  instead of one to check function signatures.
- A common schema reused across the repo, like ":schema.common/theme" should be
  registered in the global registry "schema.registry" so that consumers can just
  refer to it by keyword, as if it was a built-in malli schema.
- A common schema describing status-go entities like message, notification,
  community, etc can be stored either in the respective
  "src/status_im2/contexts/*" or registered globally, or even somewhere else.
  This is yet to be defined, but since I chose not to include schemas for them,
  we can postpone this guideline.
2023-11-18 11:04:48 -03:00
Icaro Motta 17ebedd6b8 Fix: resolve our Clojure source dependencies first in the classpath (#17919)
In PR https://github.com/status-im/status-mobile/pull/17867 we have a namespace
named schema.core, but this namespace is taken by library prismatic/schema
already (see https://github.com/plumatic/schema/tree/master/src/cljc/schema), a
library used by our direct dependency on bidi 2.1.6. This leads to a broken
build where the ClojureScript compiler reports undeclared vars
(https://clojurescript.org/reference/compiler-options#warnings).

We change the order Java resolves dependencies via the classpath mechanism. We
now first resolve our own Clojure sources, and then project dependencies.
2023-11-18 08:50:11 -03:00
Jamie CapraniandUlises M 2e2d15adfb chore: add floating button page component (#17737)
Co-authored-by: Ulises M <ulises.ssb@hotmail.com>
2023-11-17 09:16:55 -08:00
Yevheniia Berdnyk 1d1d586223 e2e: added back status link opening 2023-11-17 18:09:57 +02:00
John Ngei 88dd5e04bb enable biometric when user signs in with syncing (#17627) 2023-11-17 17:58:51 +03:00
105 changed files with 1423 additions and 273 deletions
+12 -4
View File
@@ -16,10 +16,18 @@
:clj-kondo-config {:level :error}
:cond-else {:level :error}
:consistent-alias {:level :error
:aliases {clojure.string string
clojure.set set
clojure.walk walk
taoensso.timbre log}}
:aliases {clojure.set set
clojure.string string
clojure.walk walk
malli.core malli
malli.dev.pretty malli.pretty
malli.dev.virhe malli.virhe
malli.error malli.error
malli.generator malli.generator
malli.transform malli.transform
malli.util malli.util
schema.core schema
taoensso.timbre log}}
:deprecated-namespace {:level :warning}
:docstring-blank {:level :error}
:equals-true {:level :error}
+2
View File
@@ -0,0 +1,2 @@
{:lint-as {malli.experimental/defn schema.core/defn}
:linters {:unresolved-symbol {:exclude [(malli.core/=>)]}}}
+1
View File
@@ -36,6 +36,7 @@
"deftest-sub" :arg1-body
"wait-for" :arg1-body
"with-deps-check" :arg1-body
"schema/=>" :arg1-body
"->" [:noarg1-body
{:list {:constant-pair? false :force-nl? false}
:next-inner-restore [[:list :constant-pair?]]}]
+48 -12
View File
@@ -45,11 +45,20 @@
},
{
"path": "borkdude/edamame/1.1.17/edamame-1.1.17",
"path": "borkdude/dynaload/0.3.5/dynaload-0.3.5",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "9087f7abf0104e0354d7db7fc4576608eac558f4",
"sha256": "1n1872i240lakn4pzsag4grf7bv7lcsipmqllxd9m4k1zp3dgla1"
"sha1": "accd696ba364b850b4d92e38f5a34d0e828a0ad1",
"sha256": "0k62m1f29xfh3cp67w7kcvkp5aj35simi8kf95ycvkmgp76w11q8"
}
},
{
"path": "borkdude/edamame/1.3.23/edamame-1.3.23",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "254d023e97ed438f0f44532b5a06d928d031ede4",
"sha256": "0l7mxza2nimslhg0qh6jp7gmb8sd6l89fk5d1zvzq2xskscq2vly"
}
},
@@ -81,20 +90,20 @@
},
{
"path": "cider/cider-nrepl/0.29.0/cider-nrepl-0.29.0",
"path": "cider/cider-nrepl/0.25.3/cider-nrepl-0.25.3",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "45f6034b26a14138e74145b7a4059628c0fedcd1",
"sha256": "1dy1l6y8cb8xiqq97a4lf8giyiicq4wfl4s2lxn5fb6614cjxqx2"
"sha1": "5ae0efd9377a5e60c084bdaf4a2ce094f759ce23",
"sha256": "0drxf9nm23i1pcgrkwbcr09msq37csilzww38709add0hz8spjhq"
}
},
{
"path": "cider/piggieback/0.5.2/piggieback-0.5.2",
"path": "cider/piggieback/0.4.1/piggieback-0.4.1",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "ecfd5c286a85db3f059e75c37fca5722d9e26f79",
"sha256": "1ps9yf3cxmlm447hqkidjb5xry90n0wl3jk0jn28fagq31lzylkl"
"sha1": "0a02a3e2ecd7a126ab60d8a44793342f20ced79b",
"sha256": "142vl5np33akcrnn6pksi0rjfsmmi528villxsj6cwcndvybiw4m"
}
},
@@ -539,6 +548,24 @@
}
},
{
"path": "metosin/malli/0.13.0/malli-0.13.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "286c52d26a3a9d613b5f692971bd19c581cdc4b0",
"sha256": "0vaq9d6cln5j4ww302fwlgg7m647cf2j16gs2i1p1d4klbn4jddz"
}
},
{
"path": "mvxcvi/arrangement/2.1.0/arrangement-2.1.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "1bc8f3bba7a28de85f319b7d464aa8d955f44918",
"sha256": "1j1rkwrs4wm8zk9v7ilgpnyav5sipdd2bmb83sazh392blw52wpk"
}
},
{
"path": "net/cgrand/macrovich/0.2.1/macrovich-0.2.1",
"host": "https://repo.clojars.org",
@@ -728,6 +755,15 @@
}
},
{
"path": "org/clojure/test.check/1.1.1/test.check-1.1.1",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "f33d988fd57bc9c11af1952db81c10f319c91416",
"sha256": "0y2hpkj7zl4yrpsl35ifpdaja5c72b8fpjcnmdgmld9c7cb1hlcl"
}
},
{
"path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0",
"host": "https://repo1.maven.org/maven2",
@@ -909,11 +945,11 @@
},
{
"path": "refactor-nrepl/refactor-nrepl/3.6.0/refactor-nrepl-3.6.0",
"path": "refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "2b3bb82da53b5db9c2b2aa298417816b81d0ed97",
"sha256": "1ysqabmlnghki6x0636zngxza2d83c85276wp9ma9wk183mkv52a"
"sha1": "6bc3441afc94f7ca024e41a864ca75e05df7e207",
"sha256": "0w8hax99y98l53mixxzx2ja0vcnhjv8dnsaz1zj3vqk775ns5w6i"
}
},
+8 -4
View File
@@ -3,12 +3,13 @@ babashka/fs/0.2.16/fs-0.2.16.jar
bidi/bidi/2.1.6/bidi-2.1.6.jar
binaryage/env-config/0.2.2/env-config-0.2.2.jar
binaryage/oops/0.7.2/oops-0.7.2.jar
borkdude/edamame/1.1.17/edamame-1.1.17.jar
borkdude/dynaload/0.3.5/dynaload-0.3.5.jar
borkdude/edamame/1.3.23/edamame-1.3.23.jar
borkdude/sci.impl.reflector/0.0.1/sci.impl.reflector-0.0.1.jar
camel-snake-kebab/camel-snake-kebab/0.4.3/camel-snake-kebab-0.4.3.jar
cheshire/cheshire/5.11.0/cheshire-5.11.0.jar
cider/cider-nrepl/0.29.0/cider-nrepl-0.29.0.jar
cider/piggieback/0.5.2/piggieback-0.5.2.jar
cider/cider-nrepl/0.25.3/cider-nrepl-0.25.3.jar
cider/piggieback/0.4.1/piggieback-0.4.1.jar
clj-kondo/clj-kondo/2023.09.07/clj-kondo-2023.09.07.jar
cljs-bean/cljs-bean/1.9.0/cljs-bean-1.9.0.jar
clout/clout/2.1.2/clout-2.1.2.jar
@@ -58,6 +59,8 @@ javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar
javax/servlet/servlet-api/2.5/servlet-api-2.5.jar
javax/xml/bind/jaxb-api/2.3.0/jaxb-api-2.3.0.jar
medley/medley/0.8.2/medley-0.8.2.jar
metosin/malli/0.13.0/malli-0.13.0.jar
mvxcvi/arrangement/2.1.0/arrangement-2.1.0.jar
net/cgrand/macrovich/0.2.1/macrovich-0.2.1.jar
net/java/dev/jna/jna/5.12.1/jna-5.12.1.jar
nrepl/bencode/1.1.0/bencode-1.1.0.jar
@@ -79,6 +82,7 @@ org/clojure/data.priority-map/1.1.0/data.priority-map-1.1.0.jar
org/clojure/google-closure-library/0.0-20230227-c7c0a541/google-closure-library-0.0-20230227-c7c0a541.jar
org/clojure/google-closure-library-third-party/0.0-20230227-c7c0a541/google-closure-library-third-party-0.0-20230227-c7c0a541.jar
org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.jar
org/clojure/test.check/1.1.1/test.check-1.1.1.jar
org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.jar
org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2.jar
org/clojure/tools.cli/1.0.206/tools.cli-1.0.206.jar
@@ -99,7 +103,7 @@ org/wildfly/common/wildfly-common/1.5.2.Final/wildfly-common-1.5.2.Final.jar
prismatic/schema/1.1.7/schema-1.1.7.jar
reagent/reagent/1.2.0/reagent-1.2.0.jar
re-com/re-com/2.8.0/re-com-2.8.0.jar
refactor-nrepl/refactor-nrepl/3.6.0/refactor-nrepl-3.6.0.jar
refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0.jar
re-frame/re-frame/1.3.0/re-frame-1.3.0.jar
re-frisk-remote/re-frisk-remote/1.6.0/re-frisk-remote-1.6.0.jar
re-frisk/sente/1.15.0/sente-1.15.0.jar
+6 -3
View File
@@ -67,9 +67,12 @@ stdenv.mkDerivation {
'';
buildPhase = ''
# Assemble CLASSPATH from available clojure dependencies.
# We append 'src' so it can find the local sources.
export CLASS_PATH="$(find ${deps.clojure} \
-iname '*.jar' | tr '\n' ':')src"
# We prepend 'src' so it can find the local sources and prioritize
# our own namespaces over dependencies, given that indirect dependencies
# can also cause naming conflicts (e.g. prismatic/schema already uses
# namespace schema.core).
export CLASS_PATH="src:$(find ${deps.clojure} \
-iname '*.jar' | tr '\n' ':')"
# target must be one of the builds defined in shadow-cljs.edn
java -cp "$CLASS_PATH" clojure.main \
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+18 -2
View File
@@ -9,6 +9,7 @@
[cljs-bean "1.9.0"]
[com.cognitect/transit-cljs "0.8.280"]
[camel-snake-kebab "0.4.3"]
[metosin/malli "0.13.0"]
;; Dev dependencies
[refactor-nrepl "2.5.0"]
@@ -50,6 +51,7 @@
:after-load-async status-im2.setup.hot-reload/reload
:build-notify status-im2.setup.hot-reload/build-notify
:preloads [re-frisk-remote.preload
status-im2.setup.schema-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
@@ -71,6 +73,10 @@
:warnings {:fn-deprecated false}
:closure-defines {re-frame.trace/trace-enabled? true}
:source-map false
;; This seems to be necessary while using the REPL,
;; otherwise sometimes you'll get weird errors when
;; instrumenting functions.
:static-fns false
:infer-externs true}
;; if you want to use a real device, set your local ip
;; in the SHADOW_HOST env variable to make sure that
@@ -104,13 +110,15 @@
:output-dir "target/test"
:optimizations :simple
:target :node-test
:dev {:devtools {:preloads [status-im2.setup.schema-preload]}}
;; Uncomment line below to `make test-watch` a specific file
;; :ns-regexp "status-im2.subs.messages-test$"
:main status-im.test-runner/main
;; set :ui-driven to true to let shadow-cljs inject node-repl
:ui-driven true
:closure-defines
{status-im2.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
{schema.core/throw-on-error? true
status-im2.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
status-im2.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN"
status-im2.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"
status-im2.config/ALCHEMY_ARBITRUM_GOERLI_TOKEN #shadow/env "ALCHEMY_ARBITRUM_GOERLI_TOKEN"
@@ -139,9 +147,17 @@
:compiler-options {:optimizations :simple
:source-map false}}
:component-test {:target :npm-module
:entries [quo.core-spec status-im2.core-spec]
:entries [;; We need to tell shadow-cljs to compile
;; the preloads namespace because it will
;; be used directly by Jest in the option
;; setupFilesAfterEnv.
status-im2.setup.schema-preload
quo.core-spec
status-im2.core-spec]
:ns-regexp "component-spec$"
:output-dir "component-spec"
:closure-defines {schema.core/throw-on-error? true}
:compiler-options {:warnings-as-errors false
:static-fns false
:infer-externs true}}}}
@@ -3,18 +3,22 @@
[quo.components.counter.step.view :as step]
[test-helpers.component :as h]))
(defn render
[component]
(h/render-with-theme-provider component :dark))
(h/describe "step component"
(h/test "default render of step component"
(h/render [step/view {} nil])
(render [step/view {} nil])
(-> (h/expect (h/query-by-label-text :step-counter))
(h/is-truthy)))
(h/test "renders step with a string value"
(h/render [step/view {} "1"])
(render [step/view {} "1"])
(-> (h/expect (h/get-by-text "1"))
(h/is-truthy)))
(h/test "renders step with an integer value"
(h/render [step/view {} 1])
(render [step/view {} 1])
(-> (h/expect (h/get-by-text "1"))
(h/is-truthy))))
+20 -3
View File
@@ -2,10 +2,24 @@
(:require
[quo.components.counter.step.style :as style]
[quo.components.markdown.text :as text]
[quo.theme :as theme]
quo.theme
[react-native.core :as rn]
[schema.core :as schema]
[utils.number]))
(def ?schema
[:=>
[:catn
[:props
[:map {:closed true}
[:accessibility-label {:optional true} [:maybe :keyword]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:in-blur-view? {:optional true} [:maybe :boolean]]
[:theme :schema.common/theme]
[:type {:optional true} [:enum :active :complete :neutral]]]]
[:value [:maybe [:or :string :int]]]]
:any])
(defn- view-internal
[{:keys [type accessibility-label theme in-blur-view? customization-color]} value]
(let [type (or type :neutral)
@@ -23,6 +37,9 @@
[text/text
{:weight :medium
:size :label
:style {:color (style/text-color type theme)}} label]]))
:style {:color (style/text-color type theme)}}
label]]))
(def view (theme/with-theme view-internal))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal ?schema)))
@@ -19,3 +19,7 @@
:background-color colors/danger-50
:align-items :center
:justify-content :center})
(def empty-state
{:width 375
:height 116})
@@ -2,11 +2,12 @@
(:require
[quo.components.graph.utils :as utils]
[quo.components.graph.wallet-graph.style :as style]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.foundations.resources :as resources]
[quo.theme :as quo.theme]
[react-native.charts :as charts]
[react-native.core :as rn]
[react-native.fast-image :as fast-image]
[react-native.linear-gradient :as linear-gradient]))
(defn- time-frame->max-data-points
@@ -42,11 +43,10 @@
gradient-colors [(colors/alpha line-color 0.1) (colors/alpha line-color 0)]
fill-color (colors/theme-colors colors/white colors/neutral-95)]
(if (= time-frame :empty)
[rn/view
{:accessibility-label :illustration
:style style/illustration}
[text/text {:style {:color colors/white}}
"Illustration here"]]
[fast-image/fast-image
{:style style/empty-state
:source (resources/get-themed-image :no-funds theme)
:accessibility-label :illustration}]
[rn/view
[linear-gradient/linear-gradient
{:colors gradient-colors
+16 -18
View File
@@ -3,22 +3,17 @@
[quo.components.icon :as quo.icons]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as theme]
[quo.theme :as quo.theme]
[react-native.core :as rn]))
(def themes
{:light {:default colors/neutral-50
:success colors/success-50
:error colors/danger-50}
:dark {:default colors/neutral-40
:success colors/success-60
:error colors/danger-60}})
(defn get-color
[k]
(get-in themes [(theme/get-theme) k]))
[k theme]
(case k
:success (colors/resolve-color :success theme)
:error (colors/resolve-color :danger theme)
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme)))
(defn info-message
(defn view-internal
"[info-message opts \"message\"]
opts
{:type :default/:success/:error
@@ -27,11 +22,11 @@
:text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override
:no-icon-color? false ;; disable tint color for icon"
[{:keys [type size icon text-color icon-color no-icon-color? style]} message]
[{:keys [type size theme icon text-color icon-color no-icon-color? style accessibility-label]} message]
(let [weight (if (= size :default) :regular :medium)
icon-size (if (= size :default) 16 12)
size (if (= size :default) :paragraph-2 :label)
text-color (or text-color (get-color type))
text-color (or text-color (get-color type theme))
icon-color (or icon-color text-color)]
[rn/view
{:style (merge {:flex-direction :row
@@ -42,7 +37,10 @@
:no-color no-icon-color?
:size icon-size}]
[text/text
{:size size
:weight weight
:style {:color text-color
:margin-horizontal 4}} message]]))
{:accessibility-label accessibility-label
:size size
:weight weight
:style {:color text-color
:margin-horizontal 4}} message]]))
(def info-message (quo.theme/with-theme view-internal))
@@ -22,9 +22,10 @@
(h/is-truthy (h/get-by-text "test description")))
(h/test "renders step component when step-number is valid and type is step"
(h/render [list/view
{:type :step
:step-number 1}])
(h/render-with-theme-provider [list/view
{:type :step
:step-number 1}]
:dark)
(h/is-truthy (h/get-by-label-text :step-counter)))
(h/test "renders decription with a context tag component and description after the tag"
@@ -1,6 +1,7 @@
(ns quo.components.settings.data-item.component-spec
(:require
[quo.core :as quo]
[quo.foundations.resources :as quo.resources]
[test-helpers.component :as h]))
(h/describe
@@ -161,7 +162,8 @@
:subtitle "Description"
:icon :i/placeholder
:emoji "🎮"
:customization-color :yellow}])
:customization-color :yellow
:network-image (quo.resources/get-network :ethereum)}])
(h/is-truthy (h/query-by-label-text :description-image)))
(h/test "description emoji is visible when description is account"
@@ -7,7 +7,6 @@
[quo.components.markdown.text :as text]
[quo.components.settings.data-item.style :as style]
[quo.foundations.colors :as colors]
[quo.foundations.resources :as quo.resources]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[utils.i18n :as i18n]))
@@ -17,7 +16,8 @@
[rn/view {:style (style/loading-container size blur? theme)}])
(defn- left-subtitle
[{:keys [theme size description icon icon-color blur? subtitle customization-color emoji]}]
[{:keys [theme size description icon icon-color blur? subtitle customization-color emoji
network-image]}]
[rn/view {:style style/subtitle-container}
(when (not= :small size)
[rn/view {:style (style/subtitle-icon-container description)}
@@ -33,7 +33,7 @@
:type :default}]
:network [rn/image
{:accessibility-label :description-image
:source (quo.resources/tokens :eth)
:source network-image
:style style/image}]
nil)])
[text/text
@@ -60,7 +60,7 @@
(defn- left-side
"The description can either be given as a string `description` or a component `custom-subtitle`"
[{:keys [theme title status size blur? description custom-subtitle icon subtitle label icon-color
customization-color
customization-color network-image
emoji]
:as props}]
[rn/view {:style style/left-side}
@@ -85,7 +85,8 @@
:blur? blur?
:subtitle subtitle
:customization-color customization-color
:emoji emoji}]))])
:emoji emoji
:network-image network-image}]))])
(defn- right-side
[{:keys [label icon-right? right-icon icon-color communities-list]}]
+1 -1
View File
@@ -212,7 +212,7 @@
;;;; Counter
(def counter quo.components.counter.counter.view/view)
(def step quo.components.counter.step.view/view)
(def step #'quo.components.counter.step.view/view)
;;;; Dividers
(def divider-label quo.components.dividers.divider-label.view/view)
+9
View File
@@ -7,6 +7,15 @@
:keycard-watermark (js/require "../resources/images/ui2/keycard-watermark.png")
:bored-ape (js/require "../resources/images/mock2/bored-ape.png")})
(def ui-themed
{:no-funds
{:light (js/require "../resources/images/ui2/no-funds-light.png")
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}})
(defn get-themed-image
[k theme]
(get-in ui-themed [k theme]))
(defn get-image
[k]
(get ui k))
+68
View File
@@ -0,0 +1,68 @@
# Schemas
This document will grow to describe how we use Malli in the project and our
conventions. It's still early days 🐪
## Guidelines
### Use var quote `#'` when aliasing instrumented vars
It is common in this repository to have aliases to vars. For example, `view`
referring to `var-internal`, or `quo.core/button` referring to
`quo.components.buttons.button.view`.
If the original var being aliased is instrumented, the alias var MUST [var
quote](https://clojure.org/guides/weird_characters#_var_quote) the original var.
If you don't do this, the aliased var will not be instrumented.
```clojure
;; bad, view-internal is instrumented, but both aliases don't use a var quote.
(schema.core/=> view-internal ?schema)
(def view (quo.theme/with-theme view-internal))
(def button quo.components.buttons.button.view/button)
;; good
(schema.core/=> view-internal ?schema)
(def view (quo.theme/with-theme #'view-internal))
(def button #'quo.components.buttons.button.view/button)
```
### Prefix schema references with `?`
Prefix schema bindings and vars with a question mark `?`. This is the naming
convention used by malli itself when functions receive instances of schemas and
it's an unambiguous way to avoid naming clashes.
```clojure
;; bad
(def message-type [:enum ...])
(defn view
[message-type] ; Shadows `message-type` schema
(do-something message-type))
;; good
(def ?message-type [:enum ...])
(defn view
[message-type] ; Unambiguous naming strategy
(do-something message-type))
```
### Define schemas as functions when needed
Malli has many utility functions to manipulate schemas as data, and they will
automatically check if the schemas were already defined in the registry.
For schemas we want to conveniently access from the global registry, like
`:schema.common/theme`, they must be registered before Malli tries to use them.
```clojure
;; bad, will fail if :schema.common/bar is not registered.
(def ^:private ?foo
(malli.util/select-keys :schema.common/bar [:id :name]))
;; good, execution will be delayed until the schema ?foo is correctly registered.
(defn- ?foo
[]
(malli.util/select-keys :schema.common/bar [:id :name]))
```
+14
View File
@@ -0,0 +1,14 @@
(ns schema.common
(:require
[schema.registry :as registry]))
(def ^:private ?theme
[:enum :light :dark])
(def ^:private ?customization-color
[:or :string :keyword])
(defn register-schemas
[]
(registry/register ::theme ?theme)
(registry/register ::customization-color ?customization-color))
+17
View File
@@ -0,0 +1,17 @@
(ns schema.core)
(defmacro =>
"Similar to `malli.core/=>`, but instrumentation code will be completely removed
in non-debug environments.
`value` is first transformed via `malli.core/schema` to make sure we fail fast
and only register valid schemas."
[sym value]
`(if ^boolean js/goog.DEBUG
(try
(malli.core/=> ~sym (malli.core/schema ~value))
(catch js/Error e#
(taoensso.timbre/error "Failed to instrument function"
{:symbol ~sym :error e#})
~sym))
~sym))
+95
View File
@@ -0,0 +1,95 @@
(ns schema.core
(:require-macros schema.core)
(:require
[malli.core :as malli]
[malli.dev.pretty :as malli.pretty]
schema.state
[taoensso.timbre :as log]))
(goog-define throw-on-error? false)
(defn- ui-reporter
"Prints to STDOUT and signals a schema error should be displayed on screen."
[schema-id printer]
(let [report (malli.pretty/reporter printer)]
(fn [type data]
(report type data)
(swap! schema.state/errors conj schema-id))))
(defn- thrower
"Similar to `malli.dev.pretty/thrower`, but this reporter uses js/Error instead
of ex-info, otherwise invalid schema errors will be printed in one long and
incomprehensible line in unit test failures."
([] (thrower (malli.pretty/-printer)))
([printer]
(let [report (malli.pretty/reporter printer)]
(fn [type data]
(let [message (with-out-str (report type data))]
(throw (js/Error. (str "\n" message))))))))
(defn reporter
([]
(reporter nil nil))
([schema-id]
(reporter schema-id nil))
([schema-id opts]
(let [printer (malli.pretty/-printer
(merge {:width 60
:print-length 6
:print-level 3
:print-meta false}
opts))]
(if throw-on-error?
;; The thrower reporter should be used to short-circuit tests on schema errors.
(thrower printer)
(ui-reporter schema-id printer)))))
(defn- with-clear-schema-error
"Clears current schema error on app if args passed to `f` are valid."
[schema-id validate-input f]
(fn [& args]
(when (validate-input args)
(swap! schema.state/errors disj schema-id))
(apply f args)))
(defn instrument
"Similar to `malli/-instrument`, but will automatically clear up visible schema
errors if `f` is called with valid arguments.
We use a validator cached by `malli.core/validator`, so that validation is
performed once.
If `?schema` is invalid, then behave like a nop, log the error and return `f`.
`schema-id` is optional, but should be passed when instrumenting anonymous
functions. Consider using a namespaced keyword. For example:
(schema/instrument ::foo
(fn [x] (inc x))
[:=> [:cat :int] :int])
"
([f ?schema]
(if ^boolean js/goog.DEBUG
(let [schema-id (when (var? f) (symbol f))]
(when-not schema-id
(log/warn "Anonymous function instrumented without an explicit identifier."
{:schema ?schema}))
(instrument schema-id f ?schema))
f))
([schema-id f ?schema]
(if ^boolean js/goog.DEBUG
(try
(let [?schema (malli/schema ?schema)
{schema-input :input} (malli/-function-info ?schema)
[validate-input _] (malli/-vmap malli/validator [schema-input])]
(malli/-instrument {:schema ?schema
:report (reporter schema-id
{:title (str "Schema error at " schema-id)})}
(with-clear-schema-error schema-id validate-input f)))
(catch js/Error e
(log/error "Failed to instrument function"
{:schema-id schema-id
:error e
:function f})
f))
f)))
+26
View File
@@ -0,0 +1,26 @@
(ns schema.registry
(:refer-clojure :exclude [merge])
(:require
[malli.core :as malli]
malli.registry))
(defonce ^:private registry
(atom (malli/default-schemas)))
(defn init-global-registry
[]
(malli.registry/set-default-registry! (malli.registry/mutable-registry registry)))
(defn register
"Defines a new schema in mutable `registry`.
We normalize `?schema` by always registering it as a proper instance of
`malli.core/Schema` to avoid inconsistencies down the road."
[type ?schema]
(swap! registry assoc type (malli/schema ?schema))
?schema)
(defn merge
[& schemas]
(apply swap! registry cljs.core/merge schemas)
schemas)
+11
View File
@@ -0,0 +1,11 @@
(ns schema.state
(:require [reagent.core :as reagent]))
(def errors
"Set of schema identifiers, usually namespaced keywords. When the set is empty,
no schema errors will be displayed on the app. See `schema.view/view`."
(reagent/atom #{}))
(defn clear-errors
[]
(reset! errors #{}))
+28
View File
@@ -0,0 +1,28 @@
(ns schema.style)
(defn container
[{:keys [bottom-inset]}]
{:align-items :center
:flex-direction :row
:background-color "#cc0000"
:border-bottom-left-radius 8
:border-top-left-radius 8
:justify-content :center
:padding-vertical 6
:padding-right 16
:position :absolute
:bottom (+ 12 bottom-inset)
:right 0
:z-index 10000000})
(def icon
{:margin-horizontal 8})
(def text
{:font-family "Inter-SemiBold"
:font-size 13
:color "#ddd"})
(def text-suffix
{:font-style :italic
:font-size 9})
+18
View File
@@ -0,0 +1,18 @@
(ns schema.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
schema.state
[schema.style :as style]))
(defn view
[]
(when (seq @schema.state/errors)
[rn/pressable
{:on-press schema.state/clear-errors
:style (style/container {:bottom-inset (safe-area/get-bottom)})}
[quo/icon :i/close {:size 12 :color "#ddd" :container-style style/icon}]
[rn/text {:style style/text}
"Schema error(s)"
[rn/text {:style (merge style/text style/text-suffix)} " check logs"]]]))
+2
View File
@@ -8,6 +8,7 @@
status-im.subs.root
status-im2.setup.i18n-resources
[status-im2.setup.interceptors :as interceptors]
[status-im2.setup.schema :as schema]
status-im2.subs.root
[utils.re-frame :as rf]))
@@ -118,6 +119,7 @@
[& args]
(reset-test-data!)
(interceptors/register-global-interceptors)
(schema/setup!)
(rf/set-mergeable-keys #{:filters/load-filters
:pairing/set-installation-metadata
:dispatch-n
@@ -131,6 +131,13 @@
:accessibility-label :appearance-settings-button
:chevron true
:on-press #(re-frame/dispatch [:navigate-to :quo-preview])}])
(when config/quo-preview-enabled?
[list.item/list-item
{:icon :main-icons/appearance
:title "Status IM Components"
:accessibility-label :status-im-common-components
:chevron true
:on-press #(re-frame/dispatch [:navigate-to :status-im-preview])}])
[list.item/list-item
{:icon :main-icons/appearance
:title (i18n/label :t/appearance)
@@ -0,0 +1,32 @@
(ns status-im2.common.floating-button-page.component-spec
(:require [quo.core :as quo]
[status-im2.common.floating-button-page.view :as floating-button-page]
[test-helpers.component :as h]))
(h/describe "floating button page"
(h/test "renders with a header and standard button"
(h/render [floating-button-page/view
{:header [quo/page-nav
{:type :title-description
:title "floating button page"
:description "press right icon to swap button type"
:text-align :left
:background :blur
:icon-name :i/close}]
:footer [quo/button {} "continue"]}])
(h/is-truthy (h/get-by-text "continue"))
(h/is-truthy (h/get-by-text "floating button page")))
(h/test "renders with a header and a slide button"
(h/render [floating-button-page/view
{:header [quo/page-nav
{:type :title-description
:title "floating button page"
:description "press right icon to swap button type"
:text-align :left
:background :blur
:icon-name :i/close}]
:footer [quo/slide-button
{:track-text "We gotta slide"
:track-icon :face-id}]}])
(h/is-truthy (h/get-by-text "We gotta slide"))))
@@ -0,0 +1,17 @@
(ns status-im2.common.floating-button-page.floating-container.style
(:require [react-native.safe-area :as safe-area]))
(defn content-container
[blur? keyboard-shown?]
(let [margin-bottom (if keyboard-shown? 0 (safe-area/get-bottom))]
(cond-> {:margin-top :auto
:overflow :hidden
:margin-bottom margin-bottom
:padding-vertical 12
:padding-horizontal 20}
blur? (dissoc :padding-vertical :padding-horizontal))))
(def blur-inner-container
{:background-color :transparent ; required, otherwise blur-view will shrink
:padding-vertical 12
:padding-horizontal 20})
@@ -0,0 +1,25 @@
(ns status-im2.common.floating-button-page.floating-container.view
(:require [quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[status-im2.common.floating-button-page.floating-container.style :as style]))
(defn- blur-container
[child theme]
[blur/view
{:blur-amount 12
:blur-radius 12
:blur-type (quo.theme/theme-value :light :dark theme)}
[rn/view {:style style/blur-inner-container}
child]])
(defn view-internal
[{:keys [theme on-layout keyboard-shown? blur?]} child]
[rn/view
{:style (style/content-container blur? keyboard-shown?)
:on-layout on-layout}
(if blur?
[blur-container child theme]
child)])
(def view (quo.theme/with-theme view-internal))
@@ -0,0 +1,15 @@
(ns status-im2.common.floating-button-page.style)
(def page-container
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(def keyboard-avoiding-view
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
@@ -0,0 +1,102 @@
(ns status-im2.common.floating-button-page.view
(:require
[oops.core :as oops]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.common.floating-button-page.floating-container.view :as floating-container]
[status-im2.common.floating-button-page.style :as style]))
(defn- show-background
[{:keys [window-height keyboard-height footer-container-height content-scroll-y
content-container-height header-height keyboard-shown?]}]
(let [available-space (- window-height
(safe-area/get-top)
header-height
keyboard-height ; Already contains the bottom safe area value
footer-container-height)
scroll-view-height (- content-container-height content-scroll-y)
overlap? (< available-space scroll-view-height)]
(and keyboard-shown? overlap?)))
(defn- set-height-on-layout
[ratom]
(fn [event]
(let [height (oops/oget event "nativeEvent.layout.height")]
(reset! ratom height))))
(defn- init-keyboard-listeners
[{:keys [on-did-show]}]
(let [keyboard-will-show? (reagent/atom false)
keyboard-did-show? (reagent/atom false)
add-listener (fn [listener callback]
(oops/ocall rn/keyboard "addListener" listener callback))
will-show-listener (add-listener "keyboardWillShow"
#(reset! keyboard-will-show? true))
did-show-listener (add-listener "keyboardDidShow"
(fn [e]
(reset! keyboard-did-show? true)
(when on-did-show (on-did-show e))))
will-hide-listener (add-listener "keyboardWillHide"
#(reset! keyboard-will-show? false))
did-hide-listener (add-listener "keyboardDidHide"
#(reset! keyboard-did-show? false))
remove-listeners (fn []
(doseq [listener [will-show-listener will-hide-listener
did-show-listener did-hide-listener]]
(oops/ocall listener "remove")))]
{:keyboard-will-show? keyboard-will-show?
:keyboard-did-show? keyboard-did-show?
:remove-listeners remove-listeners}))
(defn view
[{:keys [header footer]} & children]
(reagent/with-let [window-height (:height (rn/get-window))
footer-container-height (reagent/atom 0)
header-height (reagent/atom 0)
content-container-height (reagent/atom 0)
content-scroll-y (reagent/atom 0)
keyboard-height (reagent/atom 0)
{:keys [keyboard-will-show?
keyboard-did-show?
remove-listeners]} (init-keyboard-listeners
{:on-did-show
(fn [e]
(reset! keyboard-height
(oops/oget e "endCoordinates.height")))})
set-header-height (set-height-on-layout header-height)
set-content-container-height (set-height-on-layout content-container-height)
set-footer-container-height (set-height-on-layout footer-container-height)
set-content-y-scroll (fn [event]
(reset! content-scroll-y
(oops/oget event "nativeEvent.contentOffset.y")))]
(let [keyboard-shown? (if platform/ios? @keyboard-will-show? @keyboard-did-show?)
show-background? (show-background {:window-height window-height
:footer-container-height @footer-container-height
:keyboard-height @keyboard-height
:content-scroll-y @content-scroll-y
:content-container-height @content-container-height
:header-height @header-height
:keyboard-shown? keyboard-shown?})]
[rn/view {:style style/page-container}
[rn/view {:on-layout set-header-height}
header]
[rn/scroll-view
{:on-scroll set-content-y-scroll
:scroll-event-throttle 64
:content-container-style {:flex-grow 1}}
(into [rn/view {:on-layout set-content-container-height}]
children)]
[rn/keyboard-avoiding-view
{:style style/keyboard-avoiding-view
:keyboard-vertical-offset (if platform/ios? (safe-area/get-top) 0)
:pointer-events :box-none}
[floating-container/view
{:on-layout set-footer-container-height
:keyboard-shown? keyboard-shown?
:blur? show-background?}
footer]]])
(finally
(remove-listeners))))
@@ -31,10 +31,10 @@
:on-change-text #(reset! entered-password %)}]
(when (not-empty error)
[quo/info-message
{:type :error
:size :default
:icon :i/info
:style {:margin-top 8}}
{:type :error
:size :default
:icon :i/info
:containstyle {:margin-top 8}}
(i18n/label :t/oops-wrong-password)])
[quo/button
{:container-style {:margin-bottom 12 :margin-top 40}
+13 -1
View File
@@ -79,7 +79,19 @@
:dark (js/require "../resources/images/ui2/no-contacts-to-invite-dark.png")}
:no-notifications
{:light (js/require "../resources/images/ui2/no-notifications-light.png")
:dark (js/require "../resources/images/ui2/no-notifications-dark.png")}})
:dark (js/require "../resources/images/ui2/no-notifications-dark.png")}
:no-activity
{:light (js/require "../resources/images/ui2/no-activity-light.png")
:dark (js/require "../resources/images/ui2/no-activity-dark.png")}
:no-collectibles
{:light (js/require "../resources/images/ui2/no-collectibles-light.png")
:dark (js/require "../resources/images/ui2/no-collectibles-dark.png")}
:no-dapps
{:light (js/require "../resources/images/ui2/no-dapps-light.png")
:dark (js/require "../resources/images/ui2/no-dapps-dark.png")}
:no-permissions
{:light (js/require "../resources/images/ui2/no-permissions-light.png")
:dark (js/require "../resources/images/ui2/no-permissions-dark.png")}})
(def mock-images
{:bored-ape (js/require "../resources/images/mock2/bored-ape.png")
@@ -0,0 +1,7 @@
(ns status-im2.common.standard-authentication.core
(:require
status-im2.common.standard-authentication.standard-auth.button.view
status-im2.common.standard-authentication.standard-auth.slide-button.view))
(def button status-im2.common.standard-authentication.standard-auth.button.view/view)
(def slide-button status-im2.common.standard-authentication.standard-auth.slide-button.view/view)
@@ -1,10 +1,6 @@
(ns status-im2.common.standard-authentication.standard-auth.view
(ns status-im2.common.standard-authentication.standard-auth.authorize
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.touch-id :as biometric]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.enter-password.view :as enter-password]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
@@ -46,39 +42,3 @@
{:on-enter-password on-enter-password
:button-icon-left auth-button-icon-left
:button-label auth-button-label}])}]))))))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
track-text
customization-color
auth-button-label
on-enter-password
on-auth-success
on-auth-fail
auth-button-icon-left
size
theme
blur?
container-style]}]
[rn/view {:style {:flex 1}}
[quo/slide-button
{:size size
:container-style container-style
:customization-color customization-color
:on-reset (when @reset-slider? #(reset! reset-slider? false))
:on-complete #(authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label})
:track-icon (if biometric-auth? :i/face-id :password)
:track-text track-text}]])))
(def view (quo.theme/with-theme view-internal))
@@ -0,0 +1,44 @@
(ns status-im2.common.standard-authentication.standard-auth.button.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
customization-color
auth-button-label
on-enter-password
on-auth-success
on-press
on-auth-fail
auth-button-icon-left
size
button-label
theme
blur?
container-style
icon-left]}]
[quo/button
{:size size
:container-style container-style
:customization-color customization-color
:icon-left icon-left
:on-press (if on-press
on-press
#(authorize/authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}))}
button-label])))
(def view (quo.theme/with-theme view-internal))
@@ -0,0 +1,43 @@
(ns status-im2.common.standard-authentication.standard-auth.slide-button.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
track-text
customization-color
auth-button-label
on-enter-password
on-auth-success
on-auth-fail
auth-button-icon-left
size
theme
blur?
container-style]}]
[rn/view {:style {:flex 1}}
[quo/slide-button
{:size size
:container-style container-style
:customization-color customization-color
:on-reset (when @reset-slider? #(reset! reset-slider? false))
:on-complete #(authorize/authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label})
:track-icon (if biometric-auth? :i/face-id :password)
:track-text track-text}]])))
(def view (quo.theme/with-theme view-internal))
@@ -1,15 +1,20 @@
(ns status-im2.contexts.onboarding.enable-biometrics.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im2.common.biometric.events :as biometric]
[status-im2.common.parallax.view :as parallax]
[status-im2.common.parallax.whitelist :as whitelist]
[status-im2.common.resources :as resources]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.contexts.onboarding.enable-biometrics.style :as style]
[status-im2.navigation.state :as state]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn page-title
[]
@@ -21,22 +26,39 @@
:description-accessibility-label :enable-biometrics-sub-title}])
(defn enable-biometrics-buttons
[insets]
[insets theme]
(let [supported-biometric-type (rf/sub [:biometric/supported-type])
bio-type-label (biometric/get-label-by-type supported-biometric-type)
profile-color (:color (rf/sub [:onboarding-2/profile]))]
profile-color (or (:color (rf/sub [:onboarding-2/profile]))
(rf/sub [:profile/customization-color]))
syncing-results? (= :syncing-results @state/root-id)]
[rn/view {:style (style/buttons insets)}
[quo/button
{:accessibility-label :enable-biometrics-button
:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])
:icon-left :i/face-id
:customization-color profile-color}
(i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]
[standard-auth/button
(merge
{:size 40
:accessibility-label :enable-biometrics-button
:icon-left :i/face-id
:customization-color profile-color
:button-label (i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})}
(if syncing-results?
{:theme theme
:blur? true
:on-enter-password (fn [entered-password]
(rf/dispatch
[:onboarding-2/authenticate-enable-biometrics
(security/safe-unmask-data
entered-password)])
(rf/dispatch [:hide-bottom-sheet]))
:auth-button-label (i18n/label :t/confirm)}
{:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])}))]
[quo/button
{:accessibility-label :maybe-later-button
:background :blur
:type :grey
:on-press #(rf/dispatch [:onboarding-2/create-account-and-login])
:on-press #(rf/dispatch (if syncing-results?
[:navigate-to-within-stack
[:enable-notifications :enable-biometrics]]
[:onboarding-2/create-account-and-login]))
:container-style {:margin-top 12}}
(i18n/label :t/maybe-later)]]))
@@ -55,12 +77,19 @@
:style (style/page-illustration width)
:source (resources/get-image :biometrics)}]))
(defn enable-biometrics
[]
(defn f-enable-biometrics
[{:keys [theme]}]
(let [insets (safe-area/get-insets)]
[rn/view {:style (style/page-container insets)}
[page-title]
(if whitelist/whitelisted?
[enable-biometrics-parallax]
[enable-biometrics-simple])
[enable-biometrics-buttons insets]]))
[enable-biometrics-buttons insets theme]]))
(defn- internale-enable-biometrics
[params]
[:f> f-enable-biometrics params])
(def view (quo.theme/with-theme internale-enable-biometrics))
@@ -51,7 +51,7 @@
[quo/page-nav
{:background :blur
:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back-within-stack :new-to-status])}]
:on-press #(rf/dispatch [:navigate-back-within-stack :enable-biometrics])}]
[page-title]
[rn/view {:style style/page-illustration}
[quo/text
+34 -3
View File
@@ -35,11 +35,30 @@
{:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
(rf/defn authenticate-enable-biometrics
{:events [:onboarding-2/authenticate-enable-biometrics]}
[{:keys [db]} password]
{:db (-> db
(assoc-in [:onboarding-2/profile :password] password)
(assoc-in [:onboarding-2/profile :syncing?] true))
:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
(rf/defn navigate-to-enable-notifications
{:events [:onboarding-2/navigate-to-enable-notifications]}
[{:keys [db]}]
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:db (dissoc db :onboarding-2/profile)
:dispatch [:navigate-to-within-stack [:enable-notifications :enable-biometrics]]}))
(rf/defn biometrics-done
{:events [:onboarding-2/biometrics-done]}
[{:keys [db]}]
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
:dispatch [:onboarding-2/create-account-and-login]})
(let [syncing? (get-in db [:onboarding-2/profile :syncing?])]
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
:dispatch (if syncing?
[:onboarding-2/finalize-setup]
[:onboarding-2/create-account-and-login])}))
(rf/defn biometrics-fail
{:events [:onboarding-2/biometrics-fail]}
@@ -83,6 +102,14 @@
[:navigate-to-within-stack [:enable-biometrics :new-to-status]]
[:onboarding-2/create-account-and-login])}))
(rf/defn navigate-to-enable-biometrics
{:events [:onboarding-2/navigate-to-enable-biometrics]}
[{:keys [db]}]
(let [supported-type (:biometric/supported-type db)]
{:dispatch (if supported-type
[:open-modal :enable-biometrics]
[:open-modal :enable-notifications])}))
(rf/defn seed-phrase-entered
{:events [:onboarding-2/seed-phrase-entered]}
[_ seed-phrase on-error]
@@ -120,6 +147,7 @@
[{:keys [db]}]
(let [masked-password (get-in db [:onboarding-2/profile :password])
key-uid (get-in db [:profile/profile :key-uid])
syncing? (get-in db [:onboarding-2/profile :syncing?])
biometric-enabled? (= (get-in db [:onboarding-2/profile :auth-method])
constants/auth-method-biometric)]
(cond-> {:db (assoc db :onboarding-2/generated-keys? true)}
@@ -127,7 +155,10 @@
(assoc :keychain/save-password-and-auth-method
{:key-uid key-uid
:masked-password masked-password
:on-success #(log/debug "successfully saved biometric")
:on-success (fn []
(if syncing?
(rf/dispatch [:onboarding-2/navigate-to-enable-notifications])
(log/error "successfully saved biometrics")))
:on-error #(log/error "failed to save biometrics"
{:key-uid key-uid
:error %})}))))
@@ -54,7 +54,7 @@
{:on-press (fn []
(when on-press
(on-press))
(rf/dispatch [:open-modal :enable-notifications]))
(rf/dispatch [:onboarding-2/navigate-to-enable-biometrics]))
:accessibility-label :continue-button
:customization-color profile-color
:container-style style/continue-button}
@@ -6,14 +6,14 @@
(defn- view-internal
[{:keys [theme]}]
[{:keys [theme title]}]
(let [logged-in? (rf/sub [:multiaccount/logged-in?])
has-profiles? (boolean (rf/sub [:profile/profiles-overview]))
root (if has-profiles? :profiles :intro)
light? (= theme :light)]
[quo/page-nav
{:type :title
:title "quo components preview"
:title title
:text-align :left
:icon-name :i/close
:right-side [{:icon-name (if light? :i/dark :i/light)
@@ -26,4 +26,4 @@
:descriptor descriptor
:blur? (:in-blur-view? @state)
:show-blur-background? (:in-blur-view? @state)}
[quo/step @state (:value @state)]])))
[quo/step (dissoc @state :value) (:value @state)]])))
@@ -487,7 +487,7 @@
(defn- main-screen
[]
[:<>
[common/navigation-bar]
[common/navigation-bar {:title "Quo components preview"}]
[rn/scroll-view {:style (style/main)}
(for [category (sort screens-categories)]
^{:key (first category)}
@@ -317,10 +317,10 @@
children)])
(defn- f-preview-container
[{:keys [state descriptor blur? blur-dark-only?
[{:keys [title state descriptor blur? blur-dark-only?
component-container-style
blur-container-style blur-view-props blur-height show-blur-background?]
:or {blur-height 200}}
:or {blur-height 200 title "quo component"}}
& children]
(let [theme (quo.theme/use-theme-value)]
(rn/use-effect (fn []
@@ -332,7 +332,7 @@
[rn/view
{:style {:top (safe-area/get-top)
:flex 1}}
[common/navigation-bar]
[common/navigation-bar {:title title}]
[rn/scroll-view
{:style (style/panel-basic)
:shows-vertical-scroll-indicator false}
@@ -1,6 +1,7 @@
(ns status-im2.contexts.quo-preview.settings.data-item
(:require
[quo.core :as quo]
[quo.foundations.resources :as quo.resources]
[reagent.core :as reagent]
[status-im2.common.resources :as resources]
[status-im2.contexts.quo-preview.preview :as preview]))
@@ -50,6 +51,7 @@
:right-icon :i/chevron-right
:emoji "🎮"
:customization-color :yellow
:network-image (quo.resources/get-network :ethereum)
:communities-list communities-list})]
(fn []
[preview/preview-container
@@ -0,0 +1,22 @@
(ns status-im2.contexts.status-im-preview.common.floating-button-page.style
(:require [quo.foundations.colors :as colors]
[react-native.safe-area :as safe-area]))
(defn container
[]
{:flex 1
:margin-top (safe-area/get-top)})
(def background-image
{:position :absolute
:top (- (safe-area/get-top))
:left 0
:right 0
:bottom 200})
(defn page-content
[height]
{:flex 1
:height height
:overflow :hidden
:background-color (colors/resolve-color :army 30)})
@@ -0,0 +1,55 @@
(ns status-im2.contexts.status-im-preview.common.floating-button-page.view
(:require [quo.core :as quo]
[re-frame.core :as rf]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.common.floating-button-page.view :as floating-button-page]
[status-im2.common.resources :as resources]
[status-im2.contexts.status-im-preview.common.floating-button-page.style :as style]))
(defn view
[]
(let [content-height (reagent/atom 450)
slide? (reagent/atom false)]
(fn []
[rn/view {:style (style/container)}
(when-not @slide?
[rn/image
{:style style/background-image
:source (resources/get-mock-image :dark-blur-bg)}])
[floating-button-page/view
{:header [quo/page-nav
{:type :title-description
:title "floating button page"
:description "press right icon to swap button type"
:text-align :left
:right-side [{:icon-name :i/swap
:on-press #(swap! slide? not)}]
:background :blur
:icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}]
:footer (if @slide?
[quo/slide-button
{:track-text "We gotta slide"
:track-icon :face-id
:container-style {:z-index 2}
:customization-color :blue
:on-complete #(js/alert "button slid")}
"Save address"]
[quo/button
{:container-style {:z-index 2}
:on-press #(js/alert "button pressed")}
"Save address"])}
[rn/view {:style (style/page-content @content-height)}
[quo/text {:size :heading-1} "Page Content"]
[quo/input
{:auto-focus true
:value ""}]
[quo/button
{:type :outline
:on-press #(swap! content-height (fn [v] (+ v 10)))}
"increase height"]
[quo/button
{:type :outline
:on-press #(swap! content-height (fn [v] (- v 10)))}
"decrease height"]]]])))
@@ -0,0 +1,59 @@
(ns status-im2.contexts.status-im-preview.main
(:refer-clojure :exclude [filter])
(:require
[quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.quo-preview.common :as common]
[status-im2.contexts.status-im-preview.common.floating-button-page.view :as floating-button-page]
[status-im2.contexts.status-im-preview.style :as style]
[utils.re-frame :as rf]))
(def screens-categories
{:common [{:name :floating-button-page
:component floating-button-page/view}]})
(defn- category-view
[]
(let [open? (reagent/atom false)
on-press #(swap! open? not)]
(fn [category]
[rn/view {:style {:margin-vertical 8}}
[quo/dropdown
{:type :grey
:state (if @open? :active :default)
:on-press on-press}
(name (key category))]
(when @open?
(for [{category-name :name} (val category)]
^{:key category-name}
[quo/button
{:type :outline
:container-style {:margin-vertical 8}
:on-press #(rf/dispatch [:navigate-to category-name])}
(name category-name)]))])))
(defn- main-screen
[]
[:<>
[common/navigation-bar {:title "Status IM components"}]
[rn/scroll-view {:style (style/main)}
(for [category (sort screens-categories)]
^{:key (first category)}
[category-view category])]])
(def screens
(->> screens-categories
(map val)
flatten
(map (fn [subcategory]
(update-in subcategory
[:options :topBar]
merge
{:visible false})))))
(def main-screens
[{:name :status-im-preview
:options {:topBar {:visible false}
:insets {:top? true}}
:component main-screen}])
@@ -0,0 +1,10 @@
(ns status-im2.contexts.status-im-preview.style
(:require
[quo.foundations.colors :as colors]))
(defn main
[]
{:flex 1
:padding-bottom 8
:padding-horizontal 16
:background-color (colors/theme-colors colors/white colors/neutral-90)})
@@ -8,7 +8,7 @@
[reagent.core :as reagent]
[status-im2.common.qr-codes.view :as qr-codes]
[status-im2.common.resources :as resources]
[status-im2.common.standard-authentication.standard-auth.view :as standard-auth]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.contexts.syncing.setup-syncing.style :as style]
[status-im2.contexts.syncing.utils :as sync-utils]
[utils.datetime :as datetime]
@@ -119,7 +119,7 @@
(i18n/label :t/copy-qr)]])
(when-not (sync-utils/valid-connection-string? @code)
[rn/view {:style style/standard-auth}
[standard-auth/view
[standard-auth/slide-button
{:blur? true
:size :size-40
:track-text (i18n/label :t/slide-to-reveal-code)
@@ -3,11 +3,10 @@
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[status-im2.common.resources :as resources]
[status-im2.contexts.wallet.account.tabs.dapps.style :as style]
[status-im2.contexts.wallet.common.empty-tab.view :as empty-tab]
[status-im2.contexts.wallet.common.temp :as temp]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.i18n :as i18n]))
(defn dapp-options
[]
@@ -23,13 +22,12 @@
(defn- view-internal
[{:keys [theme]}]
(let [dapps-list (temp/dapps-list {:on-press-icon #(rf/dispatch [:show-bottom-sheet
{:content dapp-options}])})]
(let [dapps-list []]
(if (empty? dapps-list)
[empty-tab/view
{:title (i18n/label :t/no-dapps)
:description (i18n/label :t/no-collectibles-description)
:placeholder? true}]
{:title (i18n/label :t/no-dapps)
:description (i18n/label :t/no-dapps-description)
:image (resources/get-themed-image :no-dapps theme)}]
[rn/view {:style style/dapps-container}
[rn/flat-list
{:data dapps-list
@@ -1,6 +1,8 @@
(ns status-im2.contexts.wallet.account.tabs.view
(:require
[quo.theme]
[react-native.core :as rn]
[status-im2.common.resources :as resources]
[status-im2.contexts.wallet.account.tabs.about.view :as about]
[status-im2.contexts.wallet.account.tabs.dapps.view :as dapps]
[status-im2.contexts.wallet.common.activity-tab.view :as activity]
@@ -10,8 +12,8 @@
[status-im2.contexts.wallet.common.token-value.view :as token-value]
[utils.i18n :as i18n]))
(defn view
[{:keys [selected-tab]}]
(defn- view-internal
[{:keys [selected-tab theme]}]
(case selected-tab
:assets [rn/flat-list
{:render-fn token-value/view
@@ -20,8 +22,13 @@
:collectibles [collectibles/view]
:activity [activity/view]
:permissions [empty-tab/view
{:title (i18n/label :t/no-permissions)
:description (i18n/label :t/no-collectibles-description)
:placeholder? true}]
{:title (i18n/label :t/no-permissions)
:description (i18n/label :t/no-collectibles-description)
:image (resources/get-image
(quo.theme/theme-value :no-permissions-light
:no-permissions-dark
theme))}]
:dapps [dapps/view]
[about/view]))
(def view (quo.theme/with-theme view-internal))
@@ -25,7 +25,6 @@
[{:id :assets :label (i18n/label :t/assets) :accessibility-label :assets-tab}
{:id :collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab}
{:id :activity :label (i18n/label :t/activity) :accessibility-label :activity-tab}
{:id :permissions :label (i18n/label :t/permissions) :accessibility-label :permissions}
{:id :dapps :label (i18n/label :t/dapps) :accessibility-label :dapps}
{:id :about :label (i18n/label :t/about) :accessibility-label :about}])
@@ -0,0 +1,38 @@
(ns status-im2.contexts.wallet.add-address-to-watch.component-spec
(:require
[re-frame.core :as re-frame]
[status-im2.contexts.wallet.add-address-to-watch.view :as add-address-to-watch]
[test-helpers.component :as h]))
(defn setup-subs
[subscriptions]
(doseq [keyval subscriptions]
(re-frame/reg-sub
(key keyval)
(fn [_] (val keyval)))))
(h/describe "select address for watch only account"
(h/test "validation messages show for already used addressed"
(setup-subs {:wallet/scanned-address nil
:wallet/addresses (set
["0x12E838Ae1f769147b12956485dc56e57138f3AC8"
"0x22E838Ae1f769147b12956485dc56e57138f3AC8"])
:profile/customization-color :blue})
(h/render [add-address-to-watch/view])
(h/is-falsy (h/query-by-label-text :error-message))
(h/fire-event :change-text
(h/get-by-label-text :add-address-to-watch)
"0x12E838Ae1f769147b12956485dc56e57138f3AC8")
(h/is-truthy (h/get-by-translation-text :address-already-in-use))))
(h/test "validation messages show for invalid address"
(setup-subs {:wallet/scanned-address nil
:wallet/addresses (set
["0x12E838Ae1f769147b12956485dc56e57138f3AC8"
"0x22E838Ae1f769147b12956485dc56e57138f3AC8"])
:profile/customization-color :blue})
(h/render [add-address-to-watch/view])
(h/is-falsy (h/query-by-label-text :error-message))
(h/fire-event :change-text (h/get-by-label-text :add-address-to-watch) "0x12E838Ae1f769147b")
(h/is-truthy (h/get-by-translation-text :invalid-address)))
@@ -3,18 +3,7 @@
(def container
{:flex 1})
(def input
{:margin-right 12
:flex 1})
(def data-item
{:margin-horizontal 20
:padding-vertical 8
:padding-horizontal 12})
(defn button-container
[bottom]
{:position :absolute
:bottom (+ bottom 12)
:left 20
:right 20})
@@ -3,7 +3,6 @@
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[re-frame.core :as re-frame]
[react-native.core :as rn]
[reagent.core :as reagent]
@@ -14,13 +13,12 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- view-internal
(defn view
[]
(let [{:keys [address]} (rf/sub [:get-screen-params])
number-of-accounts (count (rf/sub [:profile/wallet-accounts]))
account-name (reagent/atom (i18n/label :t/default-account-name
{:number (inc number-of-accounts)}))
address-title (i18n/label :t/watch-address)
account-color (reagent/atom (rand-nth colors/account-colors))
account-emoji (reagent/atom (emoji-picker.utils/random-emoji))
on-change-name #(reset! account-name %)
@@ -40,8 +38,9 @@
:on-change-name on-change-name
:on-change-color on-change-color
:on-change-emoji on-change-emoji
:watch-only? true
:bottom-action? true
:bottom-action-label :t/create-account
:bottom-action-label :t/add-watched-address
:bottom-action-props {:customization-color @account-color
:disabled? (string/blank? @account-name)
:on-press #(re-frame/dispatch [:navigate-to
@@ -51,11 +50,9 @@
:right-icon :i/advanced
:icon-right? true
:emoji @account-emoji
:title address-title
:title (i18n/label :t/watched-address)
:subtitle address
:status :default
:size :default
:container-style style/data-item
:on-press #(js/alert "To be implemented")}]]])))
(def view (quo.theme/with-theme view-internal))
@@ -5,13 +5,24 @@
:margin-top 12
:margin-bottom 20})
(def input-container
{:flex-direction :row
:padding-horizontal 20
:align-items :flex-end})
(def button-container
{:position :absolute
:bottom 22
:left 20
:right 20})
(def scan
{:align-self
:flex-end})
(def input
{:flex 1
:margin-right 12})
(def input-container
{:flex-direction :row
:margin-horizontal 20})
(def info-message
{:margin-top 8
:margin-left 20})
@@ -2,51 +2,114 @@
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.theme :as quo.theme]
[re-frame.core :as re-frame]
[react-native.clipboard :as clipboard]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.wallet.add-address-to-watch.style :as style]
[status-im2.contexts.wallet.common.validation :as validation]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view-internal
(defn validate-message
[addresses]
(fn [s]
(cond
(or (= s nil) (= s "")) nil
(contains? addresses s) (i18n/label :t/address-already-in-use)
(not (or (validation/eth-address? s)
(validation/ens-name? s))) (i18n/label :t/invalid-address)
:else nil)))
(defn- address-input
[{:keys [input-value validation-msg validate
clear-input]}]
(let [scanned-address (rf/sub [:wallet/scanned-address])
empty-input? (and (string/blank? @input-value)
(string/blank? scanned-address))
on-change-text (fn [new-text]
(reset! validation-msg (validate new-text))
(reset! input-value new-text)
(when (and scanned-address (not= scanned-address new-text))
(rf/dispatch [:wallet/clean-scanned-address])))
paste-on-input #(clipboard/get-string
(fn [clipboard-text]
(on-change-text clipboard-text)))]
(rn/use-effect (fn []
(when-not (string/blank? scanned-address)
(on-change-text scanned-address)))
[scanned-address])
[rn/view
{:style style/input-container}
[quo/input
{:accessibility-label :add-address-to-watch
:placeholder (i18n/label :t/address-placeholder)
:container-style style/input
:label (i18n/label :t/eth-or-ens)
:auto-capitalize :none
:multiline? true
:on-clear clear-input
:return-key-type :done
:clearable? (not empty-input?)
:on-change-text on-change-text
:button (when empty-input?
{:on-press paste-on-input
:text (i18n/label :t/paste)})
:value @input-value}]
[quo/button
{:type :outline
:on-press (fn []
(rn/dismiss-keyboard!)
(rf/dispatch [:open-modal :scan-address]))
:container-style style/scan
:size 40
:icon-only? true}
:i/scan]]))
(defn view
[]
(let [input-value (reagent/atom "")
(let [addresses (rf/sub [:wallet/addresses])
input-value (reagent/atom nil)
validate (validate-message (set addresses))
validation-msg (reagent/atom (validate
@input-value))
clear-input (fn []
(reset! input-value nil)
(reset! validation-msg nil)
(rf/dispatch [:wallet/clean-scanned-address]))
customization-color (rf/sub [:profile/customization-color])]
(rf/dispatch [:wallet/clean-scanned-address])
(fn []
[rn/view
{:style {:flex 1}}
[quo/page-nav
{:type :no-title
:icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}]
:on-press (fn []
(rf/dispatch [:wallet/clean-scanned-address])
(rf/dispatch [:navigate-back]))}]
[quo/text-combinations
{:container-style style/header-container
:title (i18n/label :t/add-address)
:description (i18n/label :t/enter-eth)}]
[rn/view {:style style/input-container}
[quo/input
{:label (i18n/label :t/eth-or-ens)
:button {:on-press (fn [] (clipboard/get-string #(reset! input-value %)))
:text (i18n/label :t/paste)}
:placeholder (str "0x123abc... " (string/lower-case (i18n/label :t/or)) " bob.eth")
:container-style {:margin-right 12
:flex 1}
:weight :monospace
:on-change-text #(reset! input-value %)
:default-value @input-value}]
[quo/button
{:icon-only? true
:type :outline} :i/scan]]
[:f> address-input
{:input-value input-value
:validate validate
:validation-msg validation-msg
:clear-input clear-input}]
(when @validation-msg
[quo/info-message
{:accessibility-label :error-message
:size :default
:icon :i/info
:type :error
:style style/info-message}
@validation-msg])
[quo/button
{:customization-color customization-color
:disabled? (clojure.string/blank? @input-value)
:on-press #(re-frame/dispatch [:navigate-to
:confirm-address-to-watch
{:address @input-value}])
:disabled? (string/blank? @input-value)
:on-press #(rf/dispatch [:navigate-to
:confirm-address-to-watch
{:address @input-value}])
:container-style style/button-container}
(i18n/label :t/continue)]])))
(def view (quo.theme/with-theme view-internal))
@@ -1,9 +1,10 @@
(ns status-im2.contexts.wallet.common.activity-tab.view
(:require
[quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[status-im2.common.resources :as resources]
[status-im2.contexts.wallet.common.empty-tab.view :as empty-tab]
[status-im2.contexts.wallet.common.temp :as temp]
[utils.i18n :as i18n]))
(defn activity-item
@@ -14,15 +15,17 @@
(merge {:on-press #(js/alert "Item pressed")}
item)]])
(defn view
[]
(let [activity-list temp/activity-list]
(defn- view-internal
[{:keys [theme]}]
(let [activity-list []]
(if (empty? activity-list)
[empty-tab/view
{:title (i18n/label :t/no-activity)
:description (i18n/label :t/empty-tab-description)
:placeholder? true}]
{:title (i18n/label :t/no-activity)
:description (i18n/label :t/empty-tab-description)
:image (resources/get-themed-image :no-activity theme)}]
[rn/flat-list
{:data activity-list
:style {:flex 1}
:render-fn activity-item}])))
(def view (quo.theme/with-theme view-internal))
@@ -1,19 +1,21 @@
(ns status-im2.contexts.wallet.common.collectibles-tab.view
(:require
[quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[status-im2.common.resources :as resources]
[status-im2.contexts.wallet.common.empty-tab.view :as empty-tab]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(defn- view-internal
[{:keys [theme]}]
(let [collectible-list (rf/sub [:wallet/collectibles])]
(if (empty? collectible-list)
[empty-tab/view
{:title (i18n/label :t/no-collectibles)
:description (i18n/label :t/no-collectibles-description)
:placeholder? true}]
{:title (i18n/label :t/no-collectibles)
:description (i18n/label :t/no-collectibles-description)
:image (resources/get-themed-image :no-collectibles theme)}]
[rn/flat-list
{:data collectible-list
:style {:flex 1}
@@ -25,3 +27,5 @@
:on-press #(rf/dispatch
[:navigate-to
:wallet-collectible])}])}])))
(def view (quo.theme/with-theme view-internal))
@@ -1,6 +1,5 @@
(ns status-im2.contexts.wallet.common.screen-base.create-or-edit-account.view
(:require [quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im2.constants :as constants]
@@ -8,12 +7,12 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- view-internal
(defn view
[{:keys [margin-top? page-nav-right-side account-name account-color account-emoji on-change-name
on-change-color
on-change-emoji on-focus on-blur section-label bottom-action?
bottom-action-label bottom-action-props
custom-bottom-action]} & children]
custom-bottom-action watch-only?]} & children]
(let [{:keys [top bottom]} (safe-area/get-insets)
margin-top (if (false? margin-top?) 0 top)
{window-width :width} (rn/get-window)]
@@ -36,7 +35,7 @@
{:customization-color account-color
:size 80
:emoji account-emoji
:type :default}]
:type (if watch-only? :watch-only :default)}]
[quo/button
{:size 32
:type :grey
@@ -81,5 +80,3 @@
:type :primary}
bottom-action-props)
(i18n/label bottom-action-label)])])]))
(def view (quo.theme/with-theme view-internal))
@@ -1,13 +1,16 @@
(ns status-im2.contexts.wallet.common.sheets.account-options.view
(:require [quo.core :as quo]
[quo.foundations.colors :as colors]
quo.theme
[react-native.clipboard :as clipboard]
[react-native.core :as rn]
[status-im2.contexts.wallet.common.sheets.account-options.style :as style]
[status-im2.contexts.wallet.common.temp :as temp]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(defn- view-internal
[{:keys [theme]}]
(let [{:keys [name color emoji address]} (rf/sub [:wallet/current-viewing-account])]
[:<>
[quo/drawer-top
@@ -26,7 +29,13 @@
:on-press #(rf/dispatch [:navigate-to :wallet-edit-account])}
{:icon :i/copy
:accessibility-label :copy-address
:label (i18n/label :t/copy-address)}
:label (i18n/label :t/copy-address)
:on-press (fn []
(rf/dispatch [:toasts/upsert
{:icon :i/correct
:icon-color (colors/resolve-color :success theme)
:text (i18n/label :t/address-copied)}])
(clipboard/set-string address))}
{:icon :i/share
:accessibility-label :share-account
:label (i18n/label :t/share-account)}
@@ -42,3 +51,5 @@
{:data temp/other-accounts
:render-fn (fn [account] [quo/account-item {:account-props account}])
:style {:margin-horizontal 8}}]]))
(def view (quo.theme/with-theme view-internal))
@@ -0,0 +1,5 @@
(ns status-im2.contexts.wallet.common.validation
(:require [status-im2.constants :as constants]))
(defn ens-name? [s] (re-find constants/regx-ens s))
(defn eth-address? [s] (re-find constants/regx-address s))
@@ -7,7 +7,7 @@
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.view :as standard-auth]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.constants :as constants]
[status-im2.contexts.emoji-picker.utils :as emoji-picker.utils]
[status-im2.contexts.wallet.common.utils :as utils]
@@ -114,7 +114,7 @@
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data primary-name @derivation-path @account-color)}]
[standard-auth/view
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color
@@ -53,10 +53,7 @@
[common.top-nav/view]
[rn/view {:style style/overview-container}
[quo/wallet-overview (temp/wallet-overview-state networks)]]
[rn/pressable
{:on-long-press #(rf/dispatch [:show-bottom-sheet
{:content temp/wallet-temporary-navigation}])}
[quo/wallet-graph {:time-frame :empty}]]
[quo/wallet-graph {:time-frame :empty}]
[rn/flat-list
{:style style/accounts-list
:content-container-style style/accounts-list-container
@@ -48,7 +48,7 @@
(fn []
(let [scanned-address (rf/sub [:wallet/scanned-address])
send-address (rf/sub [:wallet/send-address])
valid-ens-or-address? (boolean (rf/sub [:wallet/valid-ens-or-address?]))]
valid-ens-or-address? (rf/sub [:wallet/valid-ens-or-address?])]
[quo/address-input
{:on-focus #(reset! input-focused? true)
:on-blur #(reset! input-focused? false)
+2
View File
@@ -1,5 +1,7 @@
(ns status-im2.core-spec
(:require
[status-im2.common.floating-button-page.component-spec]
[status-im2.contexts.chat.messages.content.audio.component-spec]
[status-im2.contexts.communities.actions.community-options.component-spec]
[status-im2.contexts.wallet.add-address-to-watch.component-spec]
[status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec]))
+21 -9
View File
@@ -32,6 +32,7 @@
[status-im2.contexts.shell.activity-center.view :as activity-center]
[status-im2.contexts.shell.jump-to.view :as shell]
[status-im2.contexts.shell.share.view :as share]
[status-im2.contexts.status-im-preview.main :as status-im-preview]
[status-im2.contexts.syncing.find-sync-code.view :as find-sync-code]
[status-im2.contexts.syncing.how-to-pair.view :as how-to-pair]
[status-im2.contexts.syncing.scan-sync-code-page.view :as scan-sync-code-page]
@@ -46,7 +47,7 @@
[status-im2.contexts.wallet.create-account.select-keypair.view :as wallet-select-keypair]
[status-im2.contexts.wallet.create-account.view :as wallet-create-account]
[status-im2.contexts.wallet.edit-account.view :as wallet-edit-account]
[status-im2.contexts.wallet.saved-address.view :as wallet-saved-address]
[status-im2.contexts.wallet.saved-addresses.view :as wallet-saved-addresses]
[status-im2.contexts.wallet.scan-account.view :as scan-address]
[status-im2.contexts.wallet.send.select-address.view :as wallet-select-address]
[status-im2.navigation.options :as options]
@@ -161,11 +162,15 @@
:component create-password/create-password}
{:name :enable-biometrics
:options {:theme :dark
:layout options/onboarding-transparent-layout
:animations transitions/push-animations-for-transparent-background
:popGesture false}
:component enable-biometrics/enable-biometrics}
:options {:theme :dark
:layout options/onboarding-transparent-layout
:animations (merge transitions/new-to-status-modal-animations
transitions/push-animations-for-transparent-background)
:popGesture false
:modalPresentationStyle :overCurrentContext
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component enable-biometrics/view}
{:name :generating-keys
:options {:theme :dark
@@ -279,8 +284,8 @@
:options {:insets {:top? true}}
:component wallet-create-account/view}
{:name :wallet-saved-address
:component wallet-saved-address/view}
{:name :wallet-saved-addresses
:component wallet-saved-addresses/view}
{:name :wallet-select-address
:options {:modalPresentationStyle :overCurrentContext}
@@ -301,4 +306,11 @@
quo.preview/screens)
(when config/quo-preview-enabled?
quo.preview/main-screens)))
quo.preview/main-screens)
(when config/quo-preview-enabled?
status-im-preview/screens)
(when config/quo-preview-enabled?
status-im-preview/main-screens)))
+4 -1
View File
@@ -5,6 +5,7 @@
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
schema.view
[status-im.bottom-sheet.sheets :as bottom-sheets-old]
[status-im.ui.screens.popover.views :as popover]
[status-im.ui.screens.profile.visibility-status.views :as visibility-status-views]
@@ -71,7 +72,9 @@
[bottom-sheet-screen/view {:content component}]
[component])]
(when js/goog.DEBUG
[reloader/reload-view])]))))
[:<>
[reloader/reload-view]
[schema.view/view]])]))))
(def bottom-sheet
(reagent/reactify-component
+9 -7
View File
@@ -2,6 +2,7 @@
(:require
["react-native" :refer (DevSettings LogBox)]
[react-native.platform :as platform]
[status-im2.setup.schema :as schema]
[utils.re-frame :as rf]))
;; Ignore all logs, because there are lots of temporary warnings when developing and hot reloading
@@ -45,10 +46,11 @@
:group-chats/extract-membership-signature
:utils/dispatch-later
:json-rpc/call})
(when (and js/goog.DEBUG platform/ios? DevSettings)
;;on Android this method doesn't work
(when-let [nm (.-_nativeModule DevSettings)]
;;there is a bug in RN, so we have to enable it first and then disable
(.setHotLoadingEnabled ^js nm true)
(js/setTimeout #(.setHotLoadingEnabled ^js nm false) 1000))))
(when ^:boolean js/goog.DEBUG
(schema/setup!)
(when (and platform/ios? DevSettings)
;;on Android this method doesn't work
(when-let [nm (.-_nativeModule DevSettings)]
;;there is a bug in RN, so we have to enable it first and then disable
(.setHotLoadingEnabled ^js nm true)
(js/setTimeout #(.setHotLoadingEnabled ^js nm false) 1000)))))
+5 -1
View File
@@ -2,7 +2,9 @@
(:require
[re-frame.core :as re-frame]
[react-native.core :as rn]
[reagent.core :as reagent]))
[reagent.core :as reagent]
schema.state
[status-im2.setup.schema :as schema]))
(defonce cnt (reagent/atom 0))
(defonce reload-locked? (atom false))
@@ -19,6 +21,8 @@
(reset! visible true)
(reset! label "reloading UI")
(re-frame/clear-subscription-cache!)
(schema/setup!)
(schema.state/clear-errors)
(swap! cnt inc))
(defn before-reload
+104
View File
@@ -0,0 +1,104 @@
(ns status-im2.setup.schema
(:require
[malli.core :as malli]
[malli.dev.cljs :as malli.dev]
[malli.dev.pretty :as malli.pretty]
[malli.dev.virhe :as malli.virhe]
malli.error
malli.instrument
malli.util
schema.common
[schema.core :as schema]
schema.registry
[taoensso.timbre :as log]))
;;;; Formatters
;; These formatters replace the original ones provided by Malli. They are more
;; compact (less line breaks) and don't show the "More Information" section.
(defn block
"Same as `malli.dev.pretty/-block`, but adds only one line break between `text`
and `body`."
[text body printer]
[:group (malli.virhe/-text text printer) :break [:align 2 body]])
(defmethod malli.virhe/-format ::malli/explain
[_ _ {:keys [schema] :as explanation} printer]
{:body
[:group
(block "Value:" (malli.virhe/-visit (malli.error/error-value explanation printer) printer) printer)
:break :break
(block "Errors:" (malli.virhe/-visit (malli.error/humanize explanation) printer) printer)
:break :break
(block "Schema:" (malli.virhe/-visit schema printer) printer)]})
(defmethod malli.virhe/-format ::malli/invalid-input
[_ _ {:keys [args input fn-name]} printer]
{:body
(cond-> [:group
(block "Invalid function arguments:" (malli.virhe/-visit args printer) printer)
:break :break]
fn-name
(conj (block "Function Var:" (malli.virhe/-visit fn-name printer) printer)
:break
:break)
:else
(conj (block "Input Schema:" (malli.virhe/-visit input printer) printer)
:break
:break
(block "Errors:" (malli.pretty/-explain input args printer) printer)))})
(defmethod malli.virhe/-format ::malli/invalid-output
[_ _ {:keys [value args output fn-name]} printer]
{:body
(cond-> [:group
(block "Invalid function return value:" (malli.virhe/-visit value printer) printer)
:break :break]
fn-name
(conj (block "Function Var:" (malli.virhe/-visit fn-name printer) printer)
:break
:break)
:else
(conj (block "Function arguments:" (malli.virhe/-visit args printer) printer)
:break
:break
(block "Output Schema:" (malli.virhe/-visit output printer) printer)
:break
:break
(block "Errors:" (malli.pretty/-explain output value printer) printer)))})
(defn register-schemas
"Register all global schemas in `schema.registry/registry`.
Since keys in a map are unique, remember to qualify keywords. Prefer to add to
the global registry schemas for domain entities (e.g. message, chat,
notification, etc) or unambiguously useful schemas, like
`:schema.common/theme`."
[]
(schema.registry/merge (malli.util/schemas))
(schema.common/register-schemas))
(defn setup!
"Configure Malli and initializes instrumentation.
After evaluating an s-exp in the REPL that changes a function schema you'll
need to either save the file where the schema is defined and hot reload or
manually call `setup!`, otherwise you won't see any changes. It is safe and
even expected you will call `setup!` multiple times in REPLs."
[]
(try
(schema.registry/init-global-registry)
(register-schemas)
;; In theory not necessary, but sometimes in a REPL session the dev needs to
;; call unstrument! manually.
(malli.instrument/unstrument!)
(malli.dev/start! {:report (schema/reporter)})
(println "Schemas initialized.")
;; It is relatively easy to write invalid schemas, but we don't want to
;; block the app from initializing if such errors happen, at least not until
;; Malli matures in the project.
(catch js/Error e
(log/error "Failed to initialize schemas" {:error e}))))
+6
View File
@@ -0,0 +1,6 @@
(ns status-im2.setup.schema-preload
":dev/always is needed so that the compiler doesn't cache this file."
{:dev/always true}
(:require [status-im2.setup.schema :as schema]))
(schema/setup!)
+8
View File
@@ -21,6 +21,14 @@
vals
(sort-by :position)))
(rf/reg-sub
:wallet/addresses
:<- [:wallet]
:-> #(->> %
:accounts
keys
set))
(rf/reg-sub
:wallet/balances
:<- [:wallet/accounts]
@@ -167,3 +167,17 @@
:balance 3250
:tokens tokens-0x1}
(rf/sub [sub-name])))))
(h/deftest-sub :wallet/addresses
[sub-name]
(testing "returns all addresses"
(swap! rf-db/app-db
#(-> %
(assoc-in [:wallet :accounts] accounts)
(assoc-in [:wallet :current-viewing-account-address] "0x1")))
(is
(= (set ["0x1" "0x2"])
(rf/sub [sub-name])))))
+5
View File
@@ -2,6 +2,7 @@
(:require
["bignumber.js" :as BigNumber]
[clojure.string :as string]
[schema.core :as schema]
[utils.i18n :as i18n]))
;; The BigNumber version included in web3 sometimes hangs when dividing large
@@ -237,3 +238,7 @@
:else
(str amount))))
(schema/=> format-amount
[:=> [:cat [:maybe :int]]
[:maybe :string]])
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "fix/shards-pubsub-topic",
"commit-sha1": "e7d9f59db4ccbf2c301d5b8dd705943be8c73097",
"src-sha256": "0hwy10ycjgbh332h1fl5hyw8j9nds0idqk980svaj76ckgsi7dlv"
"version": "v0.171.11",
"commit-sha1": "8a4c2d8d2f17117aa0a00338e83b0f753ebf1328",
"src-sha256": "160qnl9dsl1ypvqg9w6z8wps4fvgq3qxi16piymhrlzgssdwkz8h"
}
@@ -158,20 +158,17 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
except Failed:
self.errors.append("Link message reaction is not shown for the sender")
# self.home_2.just_fyi("Check 'Open in Status' option")
# # url_to_open = 'http://status.im' # ToDo: enable when a bug with Status link is fixed
# url_to_open = 'https://coinmarketcap.com/'
# self.chat_1.send_message(url_to_open)
# chat_element = self.chat_2.chat_element_by_text(url_to_open)
# if chat_element.is_element_displayed(120):
# chat_element.click_on_link_inside_message_body()
# web_view = self.chat_2.open_in_status_button.click()
# # if not web_view.element_by_text('Private, Secure Communication').is_element_displayed(60):
# if not web_view.element_by_text_part(
# "Today's Cryptocurrency Prices by Market Cap").is_element_displayed(30):
# self.errors.append('URL was not opened from 1-1 chat')
# else:
# self.errors.append("Message with URL was not received") # ToDo: temp
self.home_2.just_fyi("Check 'Open in Status' option")
url_to_open = 'http://status.app'
self.chat_1.send_message(url_to_open)
chat_element = self.chat_2.chat_element_by_text(url_to_open)
if chat_element.is_element_displayed(120):
chat_element.click_on_link_inside_message_body()
web_view = self.chat_2.open_in_status_button.click()
if not web_view.element_by_text('Make the jump to web3').is_element_displayed(60):
self.errors.append('URL was not opened from 1-1 chat')
else:
self.errors.append("Message with URL was not received")
self.errors.verify_no_errors()

Some files were not shown because too many files have changed in this diff Show More