Compare commits

..
Author SHA1 Message Date
Lungu Cristian 627bb19313 semi-working poc 2023-11-21 00:25:37 +02:00
Cristian Lungu 6f3ef8c76b poc 2023-11-20 18:18:00 +02: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
Lungu CristianandIbrkhalil 8f8c8dede2 Composer collapsing when editing canceled/done (#17785)
* fix: composer height when entering and canceling edit

* fix: blur the composer input when canceling edit

* fix: focusing animation and composer height after blur

* fix: input height when canceling edit while unfocused

* ref: removed arbitrary keyboard check

* fix: moved edit-mentions logic to use-edit to fix unresolved mention

* fix: composer edit should put the cursor at the end

* fix: (potentially) fixing the mention not resolved during edit

* fix: emoji-kb handler changing the height when default kb appears

* Fix text content when editing and reentering chat

* prevent composer when focusing on opening chat with edit/reply

* clean

* Clauxx comments

* Apply for reply

* Lintil soup = yummy

* refactor variable name

* Extract the focusing logic from the data setting logic

* Edge case

* fix: composer mention key & edit re-enter issues

* fix: reply cancel input blur and smooth reply focus

---------

Co-authored-by: Ibrkhalil <vampirekid017@gmail.com>
2023-11-17 11:32:31 +02:00
Yevheniia Berdnyk 2e0643fabe e2e: fixes 15.11 2023-11-16 15:54:35 +02:00
Flavio Fraschetti ec4046e543 Hide channel list at token-gated communities overview (#17901)
This commit hides the categorized channel list for token-gated communities overview.
2023-11-16 10:46:17 +00:00
yqrashawn 4a44161163 chore: lint require i18n-resources first in status-im2.core (#17905) 2023-11-16 11:10:40 +08:00
Icaro Motta b9353b1ccd Fix: actually run integration tests (#17900)
Integration tests weren't running since https://github.com/status-im/status-mobile/pull/17762 was merged a week ago.

This commit also allows individual integration test namespaces to run if you change the :ns-regexp option in shadow-cljs. This is quite handy, since they are relatively slow.

Fixes https://github.com/status-im/status-mobile/issues/17895
2023-11-15 19:51:09 -03:00
jakub 5ceb7c2215 tests/appium: use requests for fetching APK
Also add more context for the error.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-11-15 18:08:11 +01:00
jakub 2043e9353b tests/appium: refactor conftest.py to be more readable
Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-11-15 18:08:10 +01:00
flexsurfer 7153e4da48 [#17854] [IOS] The tutorial video animation keeps playing after openi… (#17915) 2023-11-15 16:24:27 +01:00
Icaro Motta c7cba5b4d3 Fix: unable to join token-gated communities (#17894)
Fixes the bug by explicitly passing all available addresses to be revealed
to wakuext_requestToJoinCommunity and picking up the first available address
as the airdrop address. This is a temporary solution while we work on the
feature to allow users to choose which addresses to expose.

Fixes https://github.com/status-im/status-mobile/issues/17861

*Areas that may be impacted*: join community flows.

    ----------------------------------------------------------------------
    Community "Request to join" option is enabled.
    User holds more than X ETH.
    Anyone who holds <X> ETH is allowed to Become member in <COMMUNITY>.
    
    Expected: request to join is received by desktop client and accepted,
    mobile user joins the community.
    ----------------------------------------------------------------------
    
    ----------------------------------------------------------------------
    Community "Request to join" option is enabled.
    User holds less than X ETH.
    Anyone who holds <X> ETH is allowed to View and post in <CHANNEL>.
    
    Expected: request to join is received by desktop client and accepted, mobile
    user joins the community, but can't post in <CHANNEL>.
    ----------------------------------------------------------------------
    
    ----------------------------------------------------------------------
    Community "Request to join" option is enabled.
    No token permissions.
    
    Expected: request to join is received by desktop client and accepted,
    mobile user joins the community.
    ----------------------------------------------------------------------
2023-11-15 06:39:12 -03:00
Ulises Manuel 45df308dd0 [#17823] Refactor tokens data app-db (#17880)
* Refactor edit-account view and events:

 - Fix `(fn [])` code style.
 - Avoid overriding clojure.core/type by destructuring the `:type` key.
 - Split the toast callback to a different function.
 - `:wallet/save-account` receives `:on-success` instead of `:callback` to improve readability.

* Refactor app-db for `:wallet/tokens` & `:wallet/tokens-loading?`

- Remove the root sub `:wallet/tokens-loading?`, now it's in app-db in `[:wallet :ui :tokens-loading?]`.
- Remove the root subs `:wallet/tokens`, now the value is returned along with the account data by the sub
  `:wallet/accounts`, it's stored in app-db in `[:wallet :address "0x..." :tokens]`.
- Fix the format of the token data returned by the endpoint `"wallet_getWalletToken"` and the fn `js->clj`,
    - Addresses are no longer keywords (since keywords mustn't start with a number).
    - Keys are now kebab-case.
    - Chain-ids are no longer keywords, now they are integers.
- Update tests.

* Move logic to calculate `:wallet/balances` and change value returned

- Move logic to `status-im2.contexts.wallet.common.utils`
- The `:wallet/balances` value returned by the sub had the following structure:
  [{:address "0x1...", :balance 12345}
   {:address "0x2...", :balance 67890} ...]
  This required a helper function to get the balance for an address (`get-balance-by-address`)
  It has been changed to a map:
  {"0x1..." 12345
   "0x2..." 67890, ...}
  So now we don't need a helper function (just the hashmap itself or `clojure.core/get`).
- Because of the previous change, now the `get-balance-by-address` has been removed.
- The function `get-account-by-address` has zero uses, so it has been removed.
- The test for the sub has been updated.

* Create sub `:wallet/account-cards-data`

This sub returns a vector of maps to render the account cards in the wallet page.
This logic was previously in the `view` namespace, but it was completely calculated from
the subs `:wallet/accounts`, `:wallet/balances` and `:wallet/tokens-loading?`, so it was
clear that's a derived value.
2023-11-14 10:54:26 -06:00
Brian Sztamfater a74c934936 feat: implement summary tag component (#17858)
Signed-off-by: Brian Sztamfater <brian@status.im>
2023-11-14 11:50:58 -03:00
91 changed files with 1778 additions and 511 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?]]}]
+6 -5
View File
@@ -311,13 +311,14 @@ endef
lint: export TARGET := clojure
lint: export CLJ_LINTER_PRINT_WARNINGS ?= false
lint: ##@test Run code style checks
@sh scripts/lint-re-frame-in-quo-components.sh && \
sh scripts/lint-direct-require-component-outside-quo.sh && \
@sh scripts/lint/re-frame-in-quo-components.sh && \
sh scripts/lint/direct-require-component-outside-quo.sh && \
sh scripts/lint/require-i18n-resource-first.sh && \
clj-kondo --config .clj-kondo/config.edn --cache false --fail-level error --lint src $(if $(filter $(CLJ_LINTER_PRINT_WARNINGS),true),,| grep -v ': warning: ') && \
ALL_CLOJURE_FILES=$(call find_all_clojure_files) && \
scripts/lint_translations.clj && \
scripts/lint/translations.clj && \
zprint '{:search-config? true}' -sfc $$ALL_CLOJURE_FILES && \
sh scripts/lint-trailing-newline.sh && \
sh scripts/lint/trailing-newline.sh && \
node_modules/.bin/prettier --write .
# NOTE: We run the linter twice because of https://github.com/kkinnear/zprint/issues/271
@@ -327,7 +328,7 @@ lint-fix: ##@test Run code style checks and fix issues
zprint '{:search-config? true}' -sw $$ALL_CLOJURE_FILES && \
zprint '{:search-config? true}' -sw $$ALL_CLOJURE_FILES && \
clojure-lsp --ns-exclude-regex ".*/src/status_im2/core\.cljs$$" clean-ns && \
sh scripts/lint-trailing-newline.sh --fix && \
sh scripts/lint/trailing-newline.sh --fix && \
node_modules/.bin/prettier --write .
shadow-server: export TARGET := clojure
+1 -1
View File
@@ -1 +1 @@
1.26.0
1.25.0
+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 \
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env sh
if rg --quiet --multiline '^\(ns.*\n^\s*\(:require\n(^\s*(;|#_).*\n)*(^\s*\[status-im2\.setup\.i18n-resources\W)' src/status_im2/core.cljs; then
exit 0
elif [ $? -eq 1 ]; then
echo "status-im2.setup.i18n-resources must be loaded first (be the first one in ns :require form) in status-im2.core"
echo "For more info, check the comment here https://github.com/status-im/status-mobile/pull/17618#discussion_r1361275489"
else
exit $?
fi
+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)))
+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"
@@ -0,0 +1,53 @@
(ns quo.components.tags.summary-tag.component-spec
(:require
[quo.components.tags.summary-tag.view :as summary-tag]
[test-helpers.component :as h]))
(h/describe "Summary tag component tests"
(h/test "Basic render"
(h/render [summary-tag/view])
(h/is-truthy (h/query-by-label-text :container)))
(h/test "Account view render"
(h/render [summary-tag/view
{:type :account
:label "Alice's Account"
:customization-color "#ff0000"
:emoji "🎉"}])
(h/is-truthy (h/get-by-text "Alice's Account")))
(h/test "Network view render"
(h/render [summary-tag/view
{:type :network
:label "Ethereum Network"
:image-source "path/to/ethereum-logo.png"}])
(h/is-truthy (h/get-by-text "Ethereum Network")))
(h/test "Saved address view render"
(h/render [summary-tag/view
{:type :saved-address
:label "Charlie's Wallet"
:customization-color "#00ff00"}])
(h/is-truthy (h/get-by-text "Charlie's Wallet")))
(h/test "Collectible view render"
(h/render [summary-tag/view
{:type :collectible
:label "Rare Artifact"
:image-source "path/to/artifact-image.png"}])
(h/is-truthy (h/get-by-text "Rare Artifact")))
(h/test "User view render"
(h/render [summary-tag/view
{:type :user
:label "Bob Smith"
:image-source "path/to/profile-pic.png"
:customization-color "#0000ff"}])
(h/is-truthy (h/get-by-text "Bob Smith")))
(h/test "Token view render"
(h/render [summary-tag/view
{:type :token
:label "1,000 SNT"
:image-source "path/to/token-image.png"}])
(h/is-truthy (h/get-by-text "1,000 SNT"))))
@@ -0,0 +1,29 @@
(ns quo.components.tags.summary-tag.style
(:require
[quo.foundations.colors :as colors]))
(defn main
[{:keys [type theme customization-color]}]
{:justify-content :center
:align-items :center
:height 32
:padding-left 4
:padding-right 10
:flex-direction :row
:border-radius (if (#{:account :collectible} type) 10 16)
:background-color (colors/resolve-color customization-color theme 10)})
(defn label
[type theme]
{:color (colors/theme-colors colors/neutral-100 colors/white theme)
:margin-left (if (= type :address) 6 4)})
(def collectible-image
{:width 24
:height 24
:border-radius 10})
(def token-image
{:width 24
:height 24
:border-radius 12})
@@ -0,0 +1,67 @@
(ns quo.components.tags.summary-tag.view
(:require
[quo.components.avatars.account-avatar.view :as account-avatar]
[quo.components.avatars.user-avatar.view :as user-avatar]
[quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
[quo.components.markdown.text :as text]
[quo.components.tags.summary-tag.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]))
(defn- left-view
[{:keys [label type customization-color emoji image-source]}]
(case type
:account
[account-avatar/view
{:customization-color customization-color
:size 24
:emoji emoji
:type :default}]
:network
[rn/image
{:source image-source
:style style/token-image}]
:saved-address
[wallet-user-avatar/wallet-user-avatar
{:full-name label
:size :size-24
:customization-color customization-color}]
:collectible
[rn/image
{:source image-source
:style style/collectible-image}]
:user
[user-avatar/user-avatar
{:full-name label
:size :xs
:profile-picture image-source
:customization-color customization-color}]
:token
[rn/image
{:source image-source
:style style/token-image}]
nil))
(defn- view-internal
"Options:
- :label - string - tag label
- :customization-color - color - It will be passed down to components that
should vary based on a custom color.
- :type - :token / :user / :collectible / :saved-address / :network / :account
- :emoji - string - emoji used for displaying account avatar
- :image-source - resource - image to display on :network, :collectible :user and :token
- :theme - :light / :dark"
[{:keys [label customization-color type theme]
:as props
:or {customization-color colors/neutral-80-opa-5}}]
[rn/view
{:accessibility-label :container
:style (style/main (assoc props :customization-color customization-color))}
[left-view props]
[text/text
{:style (style/label type theme)
:weight :semi-bold
:size :heading-1} label]])
(def view (quo.theme/with-theme view-internal))
+3 -1
View File
@@ -131,6 +131,7 @@
quo.components.tags.number-tag.view
quo.components.tags.permission-tag
quo.components.tags.status-tags
quo.components.tags.summary-tag.view
quo.components.tags.tag
quo.components.tags.tags
quo.components.tags.tiny-tag.view
@@ -211,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)
@@ -360,6 +361,7 @@
(def number-tag quo.components.tags.number-tag.view/view)
(def permission-tag quo.components.tags.permission-tag/tag)
(def status-tag quo.components.tags.status-tags/status-tag)
(def summary-tag quo.components.tags.summary-tag.view/view)
(def tag quo.components.tags.tag/tag)
(def tags quo.components.tags.tags/tags)
(def tiny-tag quo.components.tags.tiny-tag.view/view)
+1
View File
@@ -73,6 +73,7 @@
[quo.components.switchers.group-messaging-card.component-spec]
[quo.components.tags.network-tags.component-spec]
[quo.components.tags.status-tags-component-spec]
[quo.components.tags.summary-tag.component-spec]
[quo.components.tags.tiny-tag.component-spec]
[quo.components.wallet.account-card.component-spec]
[quo.components.wallet.account-origin.component-spec]
+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)
+8 -4
View File
@@ -15,6 +15,8 @@
(def test-dir (.mkdtempSync fs test-dir-prefix))
(def initialized? (atom false))
(defn signal-received-callback
[a]
(re-frame/dispatch [:signals/signal-received a]))
@@ -22,10 +24,12 @@
;; We poll for signals, could not get callback working
(defn init!
[]
(.setSignalEventCallback native-status)
(js/setInterval (fn []
(.pollSignal native-status signal-received-callback)
100)))
(when-not @initialized?
(.setSignalEventCallback native-status)
(reset! initialized? true)
(js/setInterval (fn []
(.pollSignal native-status signal-received-callback)
100))))
(def status
(clj->js
@@ -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))))
+5 -2
View File
@@ -78,13 +78,16 @@
(fn [can-save?]
(if can-save?
(keychain/get-credentials
(str key-uid "-auth")
(str key-uid "-hashed" "-auth")
#(callback (if % (oops/oget % "password") auth-method-none)))
(callback nil))))))
(defn save-user-password!
[key-uid password]
(keychain/save-credentials key-uid key-uid (security/safe-unmask-data password) #()))
(keychain/save-credentials key-uid
key-uid
(security/safe-unmask-data password)
#()))
(re-frame/reg-fx
:keychain/get-user-password
@@ -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}
@@ -33,24 +33,14 @@
(rf/sub [:profile/customization-color]))
syncing-results? (= :syncing-results @state/root-id)]
[rn/view {:style (style/buttons insets)}
[standard-auth/button
[quo/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])}))]
{:on-press #(rf/dispatch [:onboarding-2/sync-enable-biometrics])})]
[quo/button
{:accessibility-label :maybe-later-button
:background :blur
@@ -35,11 +35,10 @@
{: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]}
(rf/defn sync-enable-biometrics
{:events [:onboarding-2/sync-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 %])}})
@@ -145,15 +144,15 @@
(rf/defn onboarding-new-account-finalize-setup
{:events [:onboarding-2/finalize-setup]}
[{:keys [db]}]
(let [masked-password (get-in db [:onboarding-2/profile :password])
(let [syncing? (get-in db [:onboarding-2/profile :syncing?])
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)}
biometric-enabled?
(assoc :keychain/save-password-and-auth-method
{:key-uid key-uid
{:key-uid (if syncing? (str key-uid "-hashed") key-uid)
:masked-password masked-password
:on-success (fn []
(if syncing?
@@ -42,11 +42,19 @@
{:db (assoc-in db [:profile/login :processing] true)
::login [key-uid (native-module/sha3 (security/safe-unmask-data password))]}))
(rf/defn biometry-login
{:events [:profile.login/biometry-login]}
[{:keys [db]}]
(let [{:keys [key-uid password]} (:profile/login db)]
{:db (assoc-in db [:profile/login :processing] true)
::login [key-uid (security/safe-unmask-data password)]}))
(rf/defn login-local-paired-user
{:events [:profile.login/local-paired-user]}
[{:keys [db]}]
(let [{:keys [key-uid password]} (get-in db [:syncing :profile])]
{::login [key-uid password]}))
{:db (assoc-in db [:onboarding-2/profile :password] (security/mask-data password))
::login [key-uid password]}))
(rf/defn redirect-to-root
[{:keys [db] :as cofx}]
@@ -73,12 +81,12 @@
(data-store.settings/rpc->settings settings)
profile-overview (profile.rpc/rpc->profiles-overview account)]
(rf/merge cofx
{:db (assoc db
:chats/loading? true
:networks/current-network current-network
:wallet/tokens-loading? true
:networks/networks (merge networks config/default-networks-by-id)
:profile/profile (merge profile-overview settings))}
{:db (-> db
(assoc :chats/loading? true
:networks/current-network current-network
:networks/networks (merge networks config/default-networks-by-id)
:profile/profile (merge profile-overview settings))
(assoc-in [:wallet :ui :tokens-loading?] true))}
(notifications/load-preferences)
(data-store.chats/fetch-chats-preview
{:on-success
@@ -158,14 +166,25 @@
{:on-success #(rf/dispatch [:profile.login/biometric-success])
:on-faile #(rf/dispatch [:profile.login/biometric-auth-fail])}})))
(rf/defn biometric-auth-success
{:events [:profile.login/biometric-success]}
;; TODO store the password in the hash keychain
(rf/defn backwards-compatible-biometric-auth-success
{:events [:profile.login/backwards-compatible-biometric-success]}
[{:keys [db] :as cofx}]
(let [key-uid (get-in db [:profile/login :key-uid])]
(keychain/get-user-password cofx
key-uid
#(rf/dispatch [:profile.login/get-user-password-success %]))))
(rf/defn biometric-auth-success
{:events [:profile.login/biometric-success]}
[{:keys [db] :as cofx}]
(let [key-uid (get-in db [:profile/login :key-uid])]
(keychain/get-user-password cofx
(str key-uid "-hashed")
#(if (nil? %)
(rf/dispatch [:profile.login/backwards-compatible-biometric-success])
(rf/dispatch [:profile.login/get-user-password-success %])))))
;; result of :keychain/get-auth-method above
(rf/defn get-user-password-success
{:events [:profile.login/get-user-password-success]}
@@ -175,7 +194,7 @@
cofx
{:db (assoc-in db [:profile/login :password] password)}
(navigation/init-root :progress)
(login))))
(biometry-login))))
(rf/defn biometric-auth-fail
{:events [:profile.login/biometric-auth-fail]}
@@ -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)]])))
@@ -153,6 +153,7 @@
[status-im2.contexts.quo-preview.tags.number-tag :as number-tag]
[status-im2.contexts.quo-preview.tags.permission-tag :as permission-tag]
[status-im2.contexts.quo-preview.tags.status-tags :as status-tags]
[status-im2.contexts.quo-preview.tags.summary-tag :as summary-tag]
[status-im2.contexts.quo-preview.tags.tag :as tag]
[status-im2.contexts.quo-preview.tags.tags :as tags]
[status-im2.contexts.quo-preview.tags.tiny-tag :as tiny-tag]
@@ -433,6 +434,8 @@
:component permission-tag/view}
{:name :status-tags
:component status-tags/view}
{:name :summary-tag
:component summary-tag/view}
{:name :tag
:component tag/view}
{:name :tags
@@ -484,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}
@@ -0,0 +1,66 @@
(ns status-im2.contexts.quo-preview.tags.summary-tag
(:require
[quo.core :as quo]
[quo.foundations.resources :as quo.resources]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.common.resources :as resources]
[status-im2.contexts.quo-preview.preview :as preview]))
(defn data
[type]
(case type
:token
{:customization-color "#9999991A"
:label "150 ETH"
:image-source (quo.resources/get-token :eth)}
:address
{:label "0x39c...Bd2"}
:user
{:image-source (resources/mock-images :user-picture-male4)
:customization-color :blue
:label "Mark Libot"}
:collectible
{:label "Isekai #1"
:image-source (resources/mock-images :collectible2)
:customization-color :yellow}
:network
{:image-source (quo.resources/get-network :ethereum)
:label "Mainnet"}
:saved-address
{:customization-color :flamingo
:label "Peter Lambo"}
:account
{:label "Account"
:emoji "🍿"
:customization-color :purple}))
(def descriptor
[{:key :type
:type :select
:options [{:value "Token"
:key :token}
{:value "Address"
:key :address}
{:value "User"
:key :user}
{:value "Collectible"
:key :collectible}
{:value "Network"
:key :network}
{:value "Saved address"
:key :saved-address}
{:value "Account"
:key :account}]}])
(defn view
[]
(let [state (reagent/atom
(merge {:type :token}
(data :token)))]
(fn []
[preview/preview-container
{:state state
:descriptor descriptor}
[rn/view {:style {:align-items :center}}
[quo/summary-tag (merge @state (data (:type @state)))]]])))
@@ -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)})
@@ -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,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))
@@ -1,22 +1,12 @@
(ns status-im2.contexts.wallet.common.utils
(:require [clojure.string :as string]
[status-im2.constants :as constants]))
[status-im2.constants :as constants]
[utils.number]))
(defn get-first-name
[full-name]
(first (string/split full-name #" ")))
(defn get-balance-by-address
[balances address]
(->> balances
(filter #(= (:address %) address))
first
:balance))
(defn get-account-by-address
[accounts address]
(some #(when (= (:address %) address) %) accounts))
(defn prettify-balance
[balance]
(str "$" (.toFixed (if (number? balance) balance 0) 2)))
@@ -32,3 +22,24 @@
[number-of-accounts]
(let [path (get-derivation-path number-of-accounts)]
(format-derivation-path path)))
(defn- calculate-raw-balance
[raw-balance decimals]
(if-let [n (utils.number/parse-int raw-balance nil)]
(/ n (Math/pow 10 (utils.number/parse-int decimals)))
0))
(defn- total-token-value-in-all-chains
[{:keys [balances-per-chain decimals]}]
(->> balances-per-chain
(vals)
(map #(calculate-raw-balance (:raw-balance %) decimals))
(reduce +)))
(defn calculate-balance
[tokens-in-account]
(->> tokens-in-account
(map (fn [token]
(* (total-token-value-in-all-chains token)
(-> token :market-values-per-currency :usd :price))))
(reduce +)))
@@ -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))
@@ -4,29 +4,33 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.wallet.common.screen-base.create-or-edit-account.view :as
create-or-edit-account]
[status-im2.contexts.wallet.common.sheets.network-preferences.view :as network-preferences]
[status-im2.contexts.wallet.common.screen-base.create-or-edit-account.view
:as create-or-edit-account]
[status-im2.contexts.wallet.common.sheets.network-preferences.view
:as network-preferences]
[status-im2.contexts.wallet.edit-account.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- show-save-account-toast
[updated-key theme]
(let [message (case updated-key
:name :t/edit-wallet-account-name-updated-message
:color :t/edit-wallet-account-colour-updated-message
:emoji :t/edit-wallet-account-emoji-updated-message
nil)]
(rf/dispatch [:toasts/upsert
{:id :edit-account
:icon :i/correct
:icon-color (colors/resolve-color :success theme)
:text (i18n/label message)}])))
(defn- save-account
[{:keys [theme type value account]}]
(let [edited-account-data (assoc account type value)
message (case type
:name :t/edit-wallet-account-name-updated-message
:color :t/edit-wallet-account-colour-updated-message
:emoji :t/edit-wallet-account-emoji-updated-message
nil)
callback #(rf/dispatch [:toasts/upsert
{:id :edit-account
:icon :i/correct
:icon-color (colors/resolve-color :success theme)
:text (i18n/label message)}])]
[{:keys [account updated-key new-value theme]}]
(let [edited-account-data (assoc account updated-key new-value)]
(rf/dispatch [:wallet/save-account
{:account edited-account-data
:callback callback}])))
{:account edited-account-data
:on-success #(show-save-account-toast updated-key theme)}])))
(defn- view-internal
[{:keys [theme]}]
@@ -34,22 +38,22 @@
show-confirm-button? (reagent/atom false)
on-change-color (fn [edited-color {:keys [color] :as account}]
(when (not= edited-color color)
(save-account {:account account
:type :color
:value edited-color
:theme theme})))
(save-account {:account account
:updated-key :color
:new-value edited-color
:theme theme})))
on-change-emoji (fn [edited-emoji {:keys [emoji] :as account}]
(when (not= edited-emoji emoji)
(save-account {:account account
:type :emoji
:value edited-emoji
:theme theme})))
(save-account {:account account
:updated-key :emoji
:new-value edited-emoji
:theme theme})))
on-confirm-name (fn [account]
(rn/dismiss-keyboard!)
(save-account {:account account
:type :name
:value @edited-account-name
:theme theme}))]
(save-account {:account account
:updated-key :name
:new-value @edited-account-name
:theme theme}))]
(fn []
(let [{:keys [name emoji address color]
:as account} (rf/sub [:wallet/current-viewing-account])
@@ -82,17 +86,18 @@
:right-icon :i/advanced
:card? true
:title (i18n/label :t/address)
:custom-subtitle (fn [] [quo/address-text
{:networks [{:network-name :ethereum :short-name "eth"}
{:network-name :optimism :short-name "opt"}
{:network-name :arbitrum :short-name "arb1"}]
:address address
:format :long}])
:custom-subtitle (fn []
[quo/address-text
{:networks [{:network-name :ethereum :short-name "eth"}
{:network-name :optimism :short-name "opt"}
{:network-name :arbitrum :short-name "arb1"}]
:address address
:format :long}])
:on-press (fn []
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [network-preferences/view
{:on-save #(js/alert
"calling on save")}])}]))
{:content (fn []
[network-preferences/view
{:on-save #(js/alert "calling on save")}])}]))
:container-style style/data-item}]]))))
(def view (quo.theme/with-theme view-internal))
+43 -18
View File
@@ -11,6 +11,7 @@
[taoensso.timbre :as log]
[utils.ethereum.chain :as chain]
[utils.i18n :as i18n]
[utils.number]
[utils.re-frame :as rf]
[utils.security.core :as security]
[utils.transforms :as types]))
@@ -40,7 +41,8 @@
{:db (update db :wallet dissoc :current-viewing-account-address)
:fx [[:dispatch [:navigate-back]]]}))
(rf/reg-event-fx :wallet/get-accounts-success
(rf/reg-event-fx
:wallet/get-accounts-success
(fn [{:keys [db]} [accounts]]
(let [wallet-db (get db :wallet)
new-account? (:new-account? wallet-db)
@@ -61,7 +63,8 @@
{:dispatch [:wallet/navigate-to-account navigate-to-account]
:ms 300}]])))))
(rf/reg-event-fx :wallet/get-accounts
(rf/reg-event-fx
:wallet/get-accounts
(fn [_]
{:fx [[:json-rpc/call
[{:method "accounts_getAccounts"
@@ -72,35 +75,54 @@
(rf/reg-event-fx
:wallet/save-account
(fn [_ [{:keys [account callback]}]]
(fn [_ [{:keys [account on-success]}]]
{:fx [[:json-rpc/call
[{:method "accounts_saveAccount"
:params [(data-store/<-account account)]
:on-success (fn []
(rf/dispatch [:wallet/get-accounts])
(when (fn? callback)
(callback)))
(when (fn? on-success)
(on-success)))
:on-error #(log/info "failed to save account "
{:error %
:event :wallet/save-account})}]]]}))
(rf/reg-event-fx :wallet/get-wallet-token
(rf/reg-event-fx
:wallet/get-wallet-token
(fn [{:keys [db]}]
(let [addresses (map :address (vals (get-in db [:wallet :accounts])))]
(let [addresses (->> (get-in db [:wallet :accounts])
vals
(map :address))]
{:fx [[:json-rpc/call
[{:method "wallet_getWalletToken"
:params [addresses]
:on-success [:wallet/get-wallet-token-success]
:on-success [:wallet/store-wallet-token]
:on-error #(log/info "failed to get wallet token "
{:error %
:event :wallet/get-wallet-token
:params addresses})}]]]})))
(rf/reg-event-fx :wallet/get-wallet-token-success
(fn [{:keys [db]} [tokens]]
{:db (assoc db
:wallet/tokens tokens
:wallet/tokens-loading? false)}))
(defn- fix-chain-id-keys
[token]
(update token :balances-per-chain update-keys (comp utils.number/parse-int name)))
(rf/reg-event-fx
:wallet/store-wallet-token
(fn [{:keys [db]} [raw-tokens-data]]
(let [tokens (-> raw-tokens-data
(update-keys name)
(update-vals #(cske/transform-keys csk/->kebab-case %))
(update-vals #(mapv fix-chain-id-keys %)))
add-tokens (fn [stored-accounts tokens-per-account]
(reduce-kv (fn [accounts address tokens-data]
(if (accounts address)
(update accounts address assoc :tokens tokens-data)
accounts))
stored-accounts
tokens-per-account))]
{:db (-> db
(update-in [:wallet :accounts] add-tokens tokens)
(assoc-in [:wallet :ui :tokens-loading?] false))})))
(rf/defn scan-address-success
{:events [:wallet/scan-address-success]}
@@ -122,7 +144,8 @@
:on-success on-success
:on-error #(log/info "failed to derive address " %)}]]]})))
(rf/reg-event-fx :wallet/add-account
(rf/reg-event-fx
:wallet/add-account
(fn [{:keys [db]} [password {:keys [emoji account-name color]} {:keys [public-key address path]}]]
(let [key-uid (get-in db [:profile/profile :key-uid])
sha3-pwd (native-module/sha3 (security/safe-unmask-data password))
@@ -146,11 +169,12 @@
:on-success [:wallet/get-accounts]
:on-error #(log/info "failed to create account " %)}]]]})))
(rf/reg-event-fx :wallet/derive-address-and-add-account
(rf/reg-event-fx
:wallet/derive-address-and-add-account
(fn [_ [password account-details]]
(let [on-success (fn [derived-adress-details]
(let [on-success (fn [derived-address-details]
(rf/dispatch [:wallet/add-account password account-details
(first derived-adress-details)]))]
(first derived-address-details)]))]
{:fx [[:dispatch [:wallet/create-derived-addresses password account-details on-success]]]})))
(rf/defn get-ethereum-chains
@@ -199,7 +223,8 @@
(rf/reg-event-fx :wallet/clear-stored-collectibles clear-stored-collectibles)
(rf/reg-event-fx :wallet/request-collectibles
(rf/reg-event-fx
:wallet/request-collectibles
(fn [{:keys [db]} [{:keys [start-at-index new-request?]}]]
(let [request-id 0
collectibles-filter nil
+8 -31
View File
@@ -8,7 +8,6 @@
[status-im2.contexts.wallet.common.activity-tab.view :as activity]
[status-im2.contexts.wallet.common.collectibles-tab.view :as collectibles]
[status-im2.contexts.wallet.common.temp :as temp]
[status-im2.contexts.wallet.common.utils :as utils]
[status-im2.contexts.wallet.home.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -28,9 +27,9 @@
:on-press #(rf/dispatch [:navigate-to :add-address-to-watch])
:add-divider? true}]]])
(defn- add-account-placeholder
[color]
{:customization-color color
(defn- new-account-card-data
[]
{:customization-color (rf/sub [:profile/customization-color])
:on-press #(rf/dispatch [:show-bottom-sheet {:content new-account}])
:type :add-account})
@@ -39,21 +38,6 @@
{:id :collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab}
{:id :activity :label (i18n/label :t/activity) :accessibility-label :activity-tab}])
(defn account-cards
[{:keys [accounts loading? balances profile-color]}]
(let [accounts-with-balances
(mapv
(fn [{:keys [color address] :as account}]
(assoc account
:customization-color color
:type :empty
:on-press #(rf/dispatch [:wallet/navigate-to-account address])
:loading? loading?
:balance (utils/prettify-balance
(utils/get-balance-by-address balances address))))
accounts)]
(conj accounts-with-balances (add-account-placeholder profile-color))))
(defn view
[]
(rf/dispatch [:wallet/request-collectibles
@@ -62,14 +46,10 @@
(let [top (safe-area/get-top)
selected-tab (reagent/atom (:id (first tabs-data)))]
(fn []
(let [accounts (rf/sub [:wallet/accounts])
loading? (rf/sub [:wallet/tokens-loading?])
balances (rf/sub [:wallet/balances])
profile-color (rf/sub [:profile/customization-color])
networks (rf/sub [:wallet/network-details])]
[rn/view
{:style {:margin-top top
:flex 1}}
(let [networks (rf/sub [:wallet/network-details])
account-cards-data (rf/sub [:wallet/account-cards-data])
cards (conj account-cards-data (new-account-card-data))]
[rn/view {:style {:margin-top top :flex 1}}
[common.top-nav/view]
[rn/view {:style style/overview-container}
[quo/wallet-overview (temp/wallet-overview-state networks)]]
@@ -80,10 +60,7 @@
[rn/flat-list
{:style style/accounts-list
:content-container-style style/accounts-list-container
:data (account-cards {:accounts accounts
:loading? loading?
:balances balances
:profile-color profile-color})
:data cards
:horizontal true
:separator [rn/view {:style {:width 12}}]
:render-fn quo/account-card
@@ -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]))
@@ -1,4 +1,4 @@
(ns status-im2.integration-test.chat
(ns status-im2.integration-test.chat-test
(:require
[cljs.test :refer [deftest is]]
[day8.re-frame.test :as rf-test]
@@ -16,7 +16,7 @@
"0x0402905bed83f0bbf993cee8239012ccb1a8bc86907ead834c1e38476a0eda71414eed0e25f525f270592a2eebb01c9119a4ed6429ba114e51f5cb0a28dae1adfd")
(deftest one-to-one-chat-test
(h/log-headline one-to-one-chat-test)
(h/log-headline :one-to-one-chat-test)
(rf-test/run-test-async
(h/with-app-initialized
(h/with-account
@@ -1,4 +1,4 @@
(ns status-im2.integration-test.community
(ns status-im2.integration-test.community-test
(:require [cljs.test :refer [deftest]]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
@@ -1,4 +1,4 @@
(ns status-im2.integration-test.core
(ns status-im2.integration-test.core-test
(:require
[cljs.test :refer [deftest]]
[day8.re-frame.test :as rf-test]
@@ -8,17 +8,14 @@
status-im.subs.root
[status-im.utils.test :as utils.test]
status-im2.events
status-im2.integration-test.chat
status-im2.integration-test.wallet
status-im2.navigation.core
status-im2.subs.root
[test-helpers.integration :as h]))
(utils.test/init!)
(deftest initialize-app-test
(h/log-headline :initialize-app-test)
(rf-test/run-test-async
(utils.test/init!)
(rf/dispatch [:app-started])
(rf-test/wait-for
;; use initialize-view because it has the longest avg. time and
@@ -1,6 +1,6 @@
(ns status-im2.integration-test.wallet
(ns status-im2.integration-test.wallet-test
(:require
[cljs.test :refer [deftest is]]
[cljs.test :refer [is]]
[clojure.string :as string]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
@@ -12,7 +12,9 @@
status-im2.subs.root
[test-helpers.integration :as h]))
(deftest create-wallet-account-test
;; Workaround to skip test. Switch to `deftest` when test is fixed.
(defn create-wallet-account-test
[]
(h/log-headline :create-wallet-account-test)
(rf-test/run-test-async
(h/with-app-initialized
@@ -24,7 +26,9 @@
(h/logout)
(rf-test/wait-for [::logout/logout-method]))))))
(deftest back-up-seed-phrase-test
;; Workaround to skip test. Switch to `deftest` when test is fixed.
(defn back-up-seed-phrase-test
[]
(h/log-headline :back-up-seed-phrase-test)
(rf-test/run-test-async
(h/with-app-initialized
+12 -4
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]
@@ -283,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}
@@ -305,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!)
-4
View File
@@ -145,10 +145,6 @@
(reg-root-key-sub :communities/selected-tab :communities/selected-tab)
(reg-root-key-sub :contract-communities :contract-communities)
;;wallet
(reg-root-key-sub :wallet/tokens :wallet/tokens)
(reg-root-key-sub :wallet/tokens-loading? :wallet/tokens-loading?)
;;activity center
(reg-root-key-sub :activity-center :activity-center)
+39 -35
View File
@@ -1,36 +1,19 @@
(ns status-im2.subs.wallet.wallet
(:require [re-frame.core :as re-frame]
(:require [re-frame.core :as rf]
[status-im2.contexts.wallet.common.utils :as utils]
[utils.number]))
(defn- calculate-raw-balance
[raw-balance decimals]
(if-let [n (utils.number/parse-int raw-balance nil)]
(/ n (Math/pow 10 (utils.number/parse-int decimals)))
0))
(rf/reg-sub
:wallet/ui
:<- [:wallet]
:-> :ui)
(defn- total-per-token
[item]
(reduce (fn [ac balances]
(+ (calculate-raw-balance (:rawBalance balances)
(:decimals item))
ac))
0
(vals (:balancesPerChain item))))
(rf/reg-sub
:wallet/tokens-loading?
:<- [:wallet/ui]
:-> :tokens-loading?)
(defn- calculate-balance
[address tokens]
(let [token (get tokens (keyword address))
result (reduce
(fn [acc item]
(let [total-values (* (total-per-token item)
(get-in item [:marketValuesPerCurrency :USD :price]))]
(+ acc total-values)))
0
token)]
result))
(re-frame/reg-sub
(rf/reg-sub
:wallet/accounts
:<- [:wallet]
:-> #(->> %
@@ -38,20 +21,41 @@
vals
(sort-by :position)))
(re-frame/reg-sub
(rf/reg-sub
:wallet/addresses
:<- [:wallet]
:-> #(->> %
:accounts
keys
set))
(rf/reg-sub
:wallet/balances
:<- [:wallet/accounts]
:<- [:wallet/tokens]
(fn [[accounts tokens]]
(for [{:keys [address]} accounts]
{:address address
:balance (calculate-balance address tokens)})))
(fn [accounts]
(zipmap (map :address accounts)
(map #(-> % :tokens utils/calculate-balance) accounts))))
(re-frame/reg-sub
(rf/reg-sub
:wallet/account-cards-data
:<- [:wallet/accounts]
:<- [:wallet/balances]
:<- [:wallet/tokens-loading?]
(fn [[accounts balances tokens-loading?]]
(mapv (fn [{:keys [color address] :as account}]
(assoc account
:customization-color color
:type :empty
:on-press #(rf/dispatch [:wallet/navigate-to-account address])
:loading? tokens-loading?
:balance (utils/prettify-balance (get balances address))))
accounts)))
(rf/reg-sub
:wallet/current-viewing-account
:<- [:wallet]
:<- [:wallet/balances]
(fn [[{:keys [current-viewing-account-address] :as wallet} balances]]
(-> wallet
(get-in [:accounts current-viewing-account-address])
(assoc :balance (utils/get-balance-by-address balances current-viewing-account-address)))))
(assoc :balance (get balances current-viewing-account-address)))))
+97 -92
View File
@@ -8,43 +8,35 @@
(use-fixtures :each
{:before #(reset! rf-db/app-db {})})
(def tokens
{:0x1 [{:decimals 1
:symbol "ETH"
:name "Ether"
:balancesPerChain {:1 {:rawBalance "20"
:hasError false}
:2 {:rawBalance "10"
:hasError false}}
:marketValuesPerCurrency {:USD {:price 1000}}} ;; total should be 3000
{:decimals 2
:symbol "DAI"
:name "Dai Stablecoin"
:balancesPerChain {:1 {:rawBalance "100"
:hasError false}
:2 {:rawBalance "150"
:hasError false}}
:marketValuesPerCurrency {:USD {:price 100}}}] ;; total should be 250
:0x2 [{:decimals 3
:symbol "ETH"
:name "Ether"
:balancesPerChain {:1 {:rawBalance "2500"
:hasError false}
:2 {:rawBalance "3000"
:hasError false}
:3 {:rawBalance "<nil>"
:hasError false}}
:marketValuesPerCurrency {:USD {:price 200}}} ;; total should be 1100
{:decimals 10
:symbol "DAI"
:name "Dai Stablecoin"
:balancesPerChain {:1 {:rawBalance "10000000000"
:hasError false}
:2 {:rawBalance "0"
:hasError false}
:3 {:rawBalance "<nil>"
:hasError false}}
:marketValuesPerCurrency {:USD {:price 1000}}}]}) ;; total should be 1000
(def tokens-0x1
[{:decimals 1
:symbol "ETH"
:name "Ether"
:balances-per-chain {1 {:raw-balance "20" :has-error false}
2 {:raw-balance "10" :has-error false}}
:market-values-per-currency {:usd {:price 1000}}}
{:decimals 2
:symbol "DAI"
:name "Dai Stablecoin"
:balances-per-chain {1 {:raw-balance "100" :has-error false}
2 {:raw-balance "150" :has-error false}}
:market-values-per-currency {:usd {:price 100}}}])
(def tokens-0x2
[{:decimals 3
:symbol "ETH"
:name "Ether"
:balances-per-chain {1 {:raw-balance "2500" :has-error false}
2 {:raw-balance "3000" :has-error false}
3 {:raw-balance "<nil>" :has-error false}}
:market-values-per-currency {:usd {:price 200}}}
{:decimals 10
:symbol "DAI"
:name "Dai Stablecoin"
:balances-per-chain {1 {:raw-balance "10000000000" :has-error false}
2 {:raw-balance "0" :has-error false}
3 {:raw-balance "<nil>" :has-error false}}
:market-values-per-currency {:usd {:price 1000}}}])
(def accounts
{"0x1" {:path "m/44'/60'/0'/0/0"
@@ -65,7 +57,8 @@
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:removed false
:tokens tokens-0x1}
"0x2" {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
@@ -84,66 +77,63 @@
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}})
:removed false
:tokens tokens-0x2}})
(h/deftest-sub :wallet/balances
[sub-name]
(testing "returns seq of maps containing :address and :balance"
(swap! rf-db/app-db #(-> %
(assoc-in [:wallet :accounts] accounts)
(assoc :wallet/tokens tokens)))
(is (= `({:address "0x1"
:balance 3250}
{:address "0x2"
:balance 2100})
(testing "a map: address->balance"
(swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts))
(is (= {"0x1" 3250 "0x2" 2100}
(rf/sub [sub-name])))))
(h/deftest-sub :wallet/accounts
[sub-name]
(testing "returns all accounts without balance"
(swap! rf-db/app-db
#(-> %
(assoc-in [:wallet :accounts] accounts)
(assoc :wallet/tokens tokens)))
(swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts))
(is
(= `({:path "m/44'/60'/0'/0/0"
:emoji "😃"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :blue
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:position 0
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
{:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x2"
:wallet false
:name "Account Two"
:type :generated
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false})
(= (list {:path "m/44'/60'/0'/0/0"
:emoji "😃"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :blue
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:position 0
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false
:tokens tokens-0x1}
{:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x2"
:wallet false
:name "Account Two"
:type :generated
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false
:tokens tokens-0x2})
(rf/sub [sub-name])))))
(h/deftest-sub :wallet/current-viewing-account
@@ -152,8 +142,8 @@
(swap! rf-db/app-db
#(-> %
(assoc-in [:wallet :accounts] accounts)
(assoc-in [:wallet :current-viewing-account-address] "0x1")
(assoc :wallet/tokens tokens)))
(assoc-in [:wallet :current-viewing-account-address] "0x1")))
(is
(= {:path "m/44'/60'/0'/0/0"
:emoji "😃"
@@ -174,5 +164,20 @@
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false
:balance 3250}
: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])))))
+12 -12
View File
@@ -4,21 +4,21 @@
(defmacro with-app-initialized
[& body]
`(if (app-initialized)
(do ~@body)
(do
(rf/dispatch [:app-started])
(rf-test/wait-for
[:profile/get-profiles-overview-success]
~@body))))
`(do
(status-im.utils.test/init!)
(if (test-helpers.integration/app-initialized)
(do ~@body)
(do
(rf/dispatch [:app-started])
(rf-test/wait-for [:profile/get-profiles-overview-success]
~@body)))))
(defmacro with-account
[& body]
`(if (messenger-started)
`(if (test-helpers.integration/messenger-started)
(do ~@body)
(do
(create-multiaccount!)
(rf-test/wait-for
[:status-im.transport.core/messenger-started]
(assert-messenger-started)
(test-helpers.integration/create-multiaccount!)
(rf-test/wait-for [:status-im.transport.core/messenger-started]
(test-helpers.integration/assert-messenger-started)
~@body))))
+1
View File
@@ -1,4 +1,5 @@
(ns test-helpers.integration
(:require-macros [test-helpers.integration])
(:require
[cljs.test :refer [is]]
[native-module.core :as native-module]
+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": "debug/resend-edits",
"commit-sha1": "f1d1726a4e12159013d72008c949fee77e48627c",
"src-sha256": "0yl33bsq7x59mp8s029qw00h0m5wfg28zk3r1w7zp3m6w4j34p3d"
"version": "v0.171.11",
"commit-sha1": "8a4c2d8d2f17117aa0a00338e83b0f753ebf1328",
"src-sha256": "160qnl9dsl1ypvqg9w6z8wps4fvgq3qxi16piymhrlzgssdwkz8h"
}
+85 -60
View File
@@ -1,7 +1,7 @@
import os
import re
import signal
import urllib.request
import requests
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime
@@ -161,6 +161,57 @@ def is_uploaded():
return True
@contextmanager
def _upload_time_limit(seconds):
def signal_handler(signum, frame):
raise TimeoutError("Apk upload took more than %s seconds" % seconds)
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
class UploadApkException(Exception):
pass
def _upload_and_check_response(apk_file_path):
with _upload_time_limit(600):
with open(apk_file_path, 'rb') as f:
resp = sauce.storage._session.request('post', '/v1/storage/upload', files={'payload': f})
try:
if resp['item']['name'] != test_suite_data.apk_name:
raise UploadApkException("Incorrect apk was uploaded to Sauce storage, response:\n%s" % resp)
except KeyError:
raise UploadApkException("Error when uploading apk to Sauce storage, response:\n%s" % resp)
def _upload_and_check_response_with_retries(apk_file_path, retries=3):
for _ in range(retries):
try:
_upload_and_check_response(apk_file_path)
break
except (ConnectionError, RemoteDisconnected):
sleep(10)
def _download_apk(url):
# Absolute path adde to handle CI runs.
apk_path = os.path.join(os.path.dirname(__file__), test_suite_data.apk_name)
print('Downloading: %s' % url)
try:
resp = requests.get(url)
resp.raise_for_status()
except requests.RequestException as err:
print(resp.text)
raise err
with open(apk_path, 'wb') as f:
f.write(resp.content)
return apk_path
def pytest_configure(config):
global option
option = config.option
@@ -180,75 +231,49 @@ def pytest_configure(config):
apibase = 'eu-central-1.saucelabs.com'
else:
raise NotImplementedError("Unknown SauceLabs datacenter")
global sauce
sauce = SauceLab('https://api.' + apibase + '/', sauce_username, sauce_access_key)
if config.getoption('log_steps'):
import logging
logging.basicConfig(level=logging.INFO)
if config.getoption('env') != 'api':
test_suite_data.apk_name = ([i for i in [i for i in config.getoption('apk').split('/')
if '.apk' in i]])[0]
if is_master(config):
pr_number = config.getoption('pr_number')
if config.getoption('testrail_report'):
if pr_number:
run_number = len(testrail_report.get_runs(pr_number)) + 1
run_name = 'PR-%s run #%s' % (pr_number, run_number)
else:
run_name = test_suite_data.apk_name
testrail_report.add_run(run_name)
if pr_number:
from github import Github
repo = Github(github_token).get_user('status-im').get_repo('status-mobile')
pull = repo.get_pull(int(pr_number))
pull.get_commits()[0].create_status(state='pending', context='Mobile e2e tests',
description='e2e tests are running')
if config.getoption('env') == 'sauce':
if not is_uploaded():
def _upload_and_check_response(apk_file_path):
@contextmanager
def _upload_time_limit(seconds):
def signal_handler(signum, frame):
raise TimeoutError("Apk upload took more than %s seconds" % seconds)
if config.getoption('env') == 'api':
return
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
test_suite_data.apk_name = ([i for i in [i for i in config.getoption('apk').split('/')
if '.apk' in i]])[0]
if not is_master(config):
return
with _upload_time_limit(600):
class UploadApkException(Exception):
pass
pr_number = config.getoption('pr_number')
if config.getoption('testrail_report'):
if pr_number:
run_number = len(testrail_report.get_runs(pr_number)) + 1
run_name = 'PR-%s run #%s' % (pr_number, run_number)
else:
run_name = test_suite_data.apk_name
testrail_report.add_run(run_name)
with open(apk_file_path, 'rb') as f:
resp = sauce.storage._session.request('post', '/v1/storage/upload',
files={'payload': f})
try:
if resp['item']['name'] != test_suite_data.apk_name:
raise UploadApkException(
"Incorrect apk was uploaded to Sauce storage, response:\n%s" % resp)
except KeyError:
raise UploadApkException(
"Error when uploading apk to Sauce storage, response:\n%s" % resp)
if pr_number:
from github import Github
repo = Github(github_token).get_user('status-im').get_repo('status-mobile')
pull = repo.get_pull(int(pr_number))
pull.get_commits()[0].create_status(
state='pending',
context='Mobile e2e tests',
description='e2e tests are running'
)
if 'http' in config.getoption('apk'):
# it works with just a file_name, but I've added full path because not sure how it'll behave on the remote run (Jenkins)
file_path, to_remove = os.path.join(os.path.dirname(__file__), test_suite_data.apk_name), True
urllib.request.urlretrieve(config.getoption('apk'),
filename=file_path) # if url is not valid it raises an error
else:
file_path, to_remove = config.getoption('apk'), False
if config.getoption('env') == 'sauce' and not is_uploaded():
apk_src = config.getoption('apk')
if apk_src.startswith('http'):
apk_path = _download_apk(apk_src)
else:
apk_path = apk_src
for _ in range(3):
try:
_upload_and_check_response(apk_file_path=file_path)
break
except (ConnectionError, RemoteDisconnected):
sleep(10)
if to_remove:
os.remove(file_path)
_upload_and_check_response_with_retries(apk_path)
if apk_src.startswith('http'):
os.remove(apk_path)
def pytest_unconfigure(config):
@@ -159,16 +159,13 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
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/'
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('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):
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")
@@ -293,7 +290,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
sent_time_variants = self.chat_2.convert_device_time_to_chat_timestamp()
if timestamp not in sent_time_variants:
self.errors.append(
'Timestamp on message %s does not correspond expected [%s]' % (timestamp, *sent_time_variants))
'Timestamp on message %s does not correspond expected %s' % (timestamp, sent_time_variants))
except NoSuchElementException:
self.errors.append("No timestamp on message %s" % messages[0])
for message in [messages[1], messages[2]]:
@@ -33,6 +33,8 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.channel = self.community_view.get_channel(self.channel_name).click()
@marks.testrail_id(703503)
@marks.xfail(reason="Curated communities not loading, https://github.com/status-im/status-mobile/issues/17852",
run=False)
def test_community_discovery(self):
self.home.navigate_back_to_home_view()
self.home.communities_tab.click()
@@ -190,8 +192,6 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Restore user with predefined communities and contacts")
self.sign_in.recover_access(passphrase=waku_user.seed, second_user=True)
self.home.just_fyi("Restore user with predefined communities and contacts")
self.home.just_fyi("Check contacts/blocked users")
self.home.chats_tab.click()
self.home.contacts_tab.click()
@@ -200,10 +200,9 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.errors.append(
"Incorrect contacts number restored: %s instead of %s" % (contacts_number, len(waku_user.contacts)))
else:
for i in range(contacts_number):
key = waku_user.contacts[i]
if not self.home.element_by_text(key).is_element_displayed(30):
self.errors.append('%s was not restored as a contact from waku backup!' % key)
for contact in waku_user.contacts:
if not self.home.element_by_text(contact).is_element_displayed(30):
self.errors.append('%s was not restored as a contact from waku backup!' % contact)
# Disabled for simple check as sometimes from waku-backup users restored with 3-random names
# self.home.click_system_back_button_until_element_is_shown()
# contact_row = self.home.contact_details_row(index=i + 1)
@@ -248,7 +247,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Check that can login with different user")
self.home.reopen_app(sign_in=False)
self.sign_in.show_profiles_button.click()
self.sign_in.show_profiles_button.wait_and_click()
self.sign_in.element_by_text(self.username).click()
self.sign_in.sign_in()
self.home.communities_tab.click()
@@ -257,7 +256,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Check that can remove user from logged out state")
self.home.reopen_app(sign_in=False)
self.sign_in.show_profiles_button.click()
self.sign_in.show_profiles_button.wait_and_click()
user_card = self.sign_in.get_user(username=self.username)
user_card.open_user_options()
self.sign_in.remove_profile_button.click()
@@ -267,7 +266,7 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Check that removed user is not shown in the list anymore")
self.home.reopen_app(sign_in=False)
self.sign_in.show_profiles_button.click()
self.sign_in.show_profiles_button.wait_and_click()
if self.sign_in.element_by_text(self.username).is_element_displayed():
self.errors.append("Removed user is re-appeared after relogin!")
+1 -1
View File
@@ -10,5 +10,5 @@ communities = {
#contacts = ['Test_contact', 'MyCustomNickname'] # enable back when https://github.com/status-im/status-mobile/issues/15500 is fixed
contacts = ['zQ3...rLSpUs', 'zQ3...mxAZUU']
contacts = ['zQ3...LSpUs', 'zQ3...xAZUU']
blocked_user = 'Clear Flat Milkweedbug'
+5 -1
View File
@@ -1,6 +1,10 @@
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect', '../test/jest/jestSetup.js'],
setupFilesAfterEnv: [
'@testing-library/jest-native/extend-expect',
'../component-spec/status_im2.setup.schema_preload.js',
'../test/jest/jestSetup.js',
],
setupFiles: [],
testPathIgnorePatterns: [],
moduleNameMapper: {
+7
View File
@@ -25,6 +25,13 @@ jest.mock('react-native-languages', () => ({
},
}));
jest.mock('react-native-static-safe-area-insets', () => ({
default: {
safeAreaInsetsTop: 0,
safeAreaInsetsBottom: 0,
},
}));
jest.mock('react-native-permissions', () => require('react-native-permissions/mock'));
jest.mock('@react-native-community/audio-toolkit', () => ({
+7 -2
View File
@@ -1565,7 +1565,8 @@
"delete-account": "Delete account",
"delete-keys-keycard": "Delete keys from Keycard",
"watch-only": "Watch-only",
"watch-address": "Watch address",
"watched-address": "Watched address",
"add-watched-address": "Add watched address",
"cant-report-bug": "Can't report a bug",
"mail-should-be-configured": "Mail client should be configured",
"check-on-block-explorer": "Check on block explorer",
@@ -2384,5 +2385,9 @@
"address-activity": "This address has activity",
"keypairs": "Keypairs",
"keypairs-description": "Select keypair to derive your new account from",
"confirm-account-origin": "Confirm account origin"
"confirm-account-origin": "Confirm account origin",
"address-placeholder": "0x123abc... or bob.eth",
"invalid-address": "Its not Ethereum address or ENS name",
"address-already-in-use": "Address already being used",
"address-copied": "Address copied"
}