Compare commits

..
Author SHA1 Message Date
Ibrkhalil a4568e5f8b Edge case 2023-11-15 11:27:00 +02:00
Ibrkhalil 4fc07bda76 Extract the focusing logic from the data setting logic 2023-11-15 11:21:41 +02:00
Ibrkhalil 17a8824251 refactor variable name 2023-11-15 11:12:33 +02:00
ibrkhalil 3ebf34580a Lintil soup = yummy 2023-11-15 11:12:33 +02:00
ibrkhalil abecc38424 Apply for reply 2023-11-15 11:12:33 +02:00
ibrkhalil 3bbaee2e2f Clauxx comments 2023-11-15 11:12:33 +02:00
Ibrkhalil b9f5f737e6 clean 2023-11-15 11:12:32 +02:00
Ibrkhalil 964b19659d prevent composer when focusing on opening chat with edit/reply 2023-11-15 11:12:32 +02:00
Ibrkhalil bb02d8698d Fix text content when editing and reentering chat 2023-11-15 11:12:32 +02:00
100 changed files with 442 additions and 1717 deletions
+4 -12
View File
@@ -16,18 +16,10 @@
:clj-kondo-config {:level :error}
:cond-else {:level :error}
:consistent-alias {:level :error
: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}}
:aliases {clojure.string string
clojure.set set
clojure.walk walk
taoensso.timbre log}}
:deprecated-namespace {:level :warning}
:docstring-blank {:level :error}
:equals-true {:level :error}
-2
View File
@@ -1,2 +0,0 @@
{:lint-as {malli.experimental/defn schema.core/defn}
:linters {:unresolved-symbol {:exclude [(malli.core/=>)]}}}
-1
View File
@@ -36,7 +36,6 @@
"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?]]}]
+5 -6
View File
@@ -311,14 +311,13 @@ 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/require-i18n-resource-first.sh && \
@sh scripts/lint-re-frame-in-quo-components.sh && \
sh scripts/lint-direct-require-component-outside-quo.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
@@ -328,7 +327,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
+12 -48
View File
@@ -45,20 +45,11 @@
},
{
"path": "borkdude/dynaload/0.3.5/dynaload-0.3.5",
"path": "borkdude/edamame/1.1.17/edamame-1.1.17",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "accd696ba364b850b4d92e38f5a34d0e828a0ad1",
"sha256": "0k62m1f29xfh3cp67w7kcvkp5aj35simi8kf95ycvkmgp76w11q8"
}
},
{
"path": "borkdude/edamame/1.3.23/edamame-1.3.23",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "254d023e97ed438f0f44532b5a06d928d031ede4",
"sha256": "0l7mxza2nimslhg0qh6jp7gmb8sd6l89fk5d1zvzq2xskscq2vly"
"sha1": "9087f7abf0104e0354d7db7fc4576608eac558f4",
"sha256": "1n1872i240lakn4pzsag4grf7bv7lcsipmqllxd9m4k1zp3dgla1"
}
},
@@ -90,20 +81,20 @@
},
{
"path": "cider/cider-nrepl/0.25.3/cider-nrepl-0.25.3",
"path": "cider/cider-nrepl/0.29.0/cider-nrepl-0.29.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "5ae0efd9377a5e60c084bdaf4a2ce094f759ce23",
"sha256": "0drxf9nm23i1pcgrkwbcr09msq37csilzww38709add0hz8spjhq"
"sha1": "45f6034b26a14138e74145b7a4059628c0fedcd1",
"sha256": "1dy1l6y8cb8xiqq97a4lf8giyiicq4wfl4s2lxn5fb6614cjxqx2"
}
},
{
"path": "cider/piggieback/0.4.1/piggieback-0.4.1",
"path": "cider/piggieback/0.5.2/piggieback-0.5.2",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "0a02a3e2ecd7a126ab60d8a44793342f20ced79b",
"sha256": "142vl5np33akcrnn6pksi0rjfsmmi528villxsj6cwcndvybiw4m"
"sha1": "ecfd5c286a85db3f059e75c37fca5722d9e26f79",
"sha256": "1ps9yf3cxmlm447hqkidjb5xry90n0wl3jk0jn28fagq31lzylkl"
}
},
@@ -548,24 +539,6 @@
}
},
{
"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",
@@ -755,15 +728,6 @@
}
},
{
"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",
@@ -945,11 +909,11 @@
},
{
"path": "refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0",
"path": "refactor-nrepl/refactor-nrepl/3.6.0/refactor-nrepl-3.6.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "6bc3441afc94f7ca024e41a864ca75e05df7e207",
"sha256": "0w8hax99y98l53mixxzx2ja0vcnhjv8dnsaz1zj3vqk775ns5w6i"
"sha1": "2b3bb82da53b5db9c2b2aa298417816b81d0ed97",
"sha256": "1ysqabmlnghki6x0636zngxza2d83c85276wp9ma9wk183mkv52a"
}
},
+4 -8
View File
@@ -3,13 +3,12 @@ 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/dynaload/0.3.5/dynaload-0.3.5.jar
borkdude/edamame/1.3.23/edamame-1.3.23.jar
borkdude/edamame/1.1.17/edamame-1.1.17.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.25.3/cider-nrepl-0.25.3.jar
cider/piggieback/0.4.1/piggieback-0.4.1.jar
cider/cider-nrepl/0.29.0/cider-nrepl-0.29.0.jar
cider/piggieback/0.5.2/piggieback-0.5.2.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
@@ -59,8 +58,6 @@ 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
@@ -82,7 +79,6 @@ 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
@@ -103,7 +99,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/2.5.0/refactor-nrepl-2.5.0.jar
refactor-nrepl/refactor-nrepl/3.6.0/refactor-nrepl-3.6.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
+3 -6
View File
@@ -67,12 +67,9 @@ stdenv.mkDerivation {
'';
buildPhase = ''
# Assemble CLASSPATH from available clojure dependencies.
# 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' ':')"
# We append 'src' so it can find the local sources.
export CLASS_PATH="$(find ${deps.clojure} \
-iname '*.jar' | tr '\n' ':')src"
# target must be one of the builds defined in shadow-cljs.edn
java -cp "$CLASS_PATH" clojure.main \
@@ -1,10 +0,0 @@
#!/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
+2 -18
View File
@@ -9,7 +9,6 @@
[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"]
@@ -51,7 +50,6 @@
: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
@@ -73,10 +71,6 @@
: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
@@ -110,15 +104,13 @@
: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
{schema.core/throw-on-error? true
status-im2.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
{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"
@@ -147,17 +139,9 @@
:compiler-options {:optimizations :simple
:source-map false}}
:component-test {:target :npm-module
: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]
:entries [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,22 +3,18 @@
[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"
(render [step/view {} nil])
(h/render [step/view {} nil])
(-> (h/expect (h/query-by-label-text :step-counter))
(h/is-truthy)))
(h/test "renders step with a string value"
(render [step/view {} "1"])
(h/render [step/view {} "1"])
(-> (h/expect (h/get-by-text "1"))
(h/is-truthy)))
(h/test "renders step with an integer value"
(render [step/view {} 1])
(h/render [step/view {} 1])
(-> (h/expect (h/get-by-text "1"))
(h/is-truthy))))
+3 -20
View File
@@ -2,24 +2,10 @@
(:require
[quo.components.counter.step.style :as style]
[quo.components.markdown.text :as text]
quo.theme
[quo.theme :as 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)
@@ -37,9 +23,6 @@
[text/text
{:weight :medium
:size :label
:style {:color (style/text-color type theme)}}
label]]))
:style {:color (style/text-color type theme)}} label]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal ?schema)))
(def view (theme/with-theme view-internal))
+19 -17
View File
@@ -3,17 +3,22 @@
[quo.components.icon :as quo.icons]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[quo.theme :as theme]
[react-native.core :as rn]))
(defn get-color
[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)))
(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 view-internal
(defn get-color
[k]
(get-in themes [(theme/get-theme) k]))
(defn info-message
"[info-message opts \"message\"]
opts
{:type :default/:success/:error
@@ -22,11 +27,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 theme icon text-color icon-color no-icon-color? style accessibility-label]} message]
[{:keys [type size icon text-color icon-color no-icon-color? style]} 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 theme))
text-color (or text-color (get-color type))
icon-color (or icon-color text-color)]
[rn/view
{:style (merge {:flex-direction :row
@@ -37,10 +42,7 @@
:no-color no-icon-color?
:size icon-size}]
[text/text
{: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))
{:size size
:weight weight
:style {:color text-color
:margin-horizontal 4}} message]]))
@@ -22,10 +22,9 @@
(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-with-theme-provider [list/view
{:type :step
:step-number 1}]
:dark)
(h/render [list/view
{:type :step
:step-number 1}])
(h/is-truthy (h/get-by-label-text :step-counter)))
(h/test "renders decription with a context tag component and description after the tag"
+1 -1
View File
@@ -212,7 +212,7 @@
;;;; Counter
(def counter quo.components.counter.counter.view/view)
(def step #'quo.components.counter.step.view/view)
(def step quo.components.counter.step.view/view)
;;;; Dividers
(def divider-label quo.components.dividers.divider-label.view/view)
-68
View File
@@ -1,68 +0,0 @@
# 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
@@ -1,14 +0,0 @@
(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
@@ -1,17 +0,0 @@
(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
@@ -1,95 +0,0 @@
(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
@@ -1,26 +0,0 @@
(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
@@ -1,11 +0,0 @@
(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
@@ -1,28 +0,0 @@
(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
@@ -1,18 +0,0 @@
(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,7 +8,6 @@
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]))
@@ -119,7 +118,6 @@
[& args]
(reset-test-data!)
(interceptors/register-global-interceptors)
(schema/setup!)
(rf/set-mergeable-keys #{:filters/load-filters
:pairing/set-installation-metadata
:dispatch-n
+1 -3
View File
@@ -225,9 +225,7 @@
(.. ^js % -nativeEvent -data)])
:on-load #(re-frame/dispatch [:browser/loading-started])
:on-error #(re-frame/dispatch [:browser/error-occured])
:injected-java-script-before-content-loaded (js-res/ethereum-provider (str network-id))
;; https://github.com/status-im/status-mobile/issues/17854
:allows-inline-media-playback true}])]
:injected-java-script-before-content-loaded (js-res/ethereum-provider (str network-id))}])]
[navigation
{:url url-original
:name name
@@ -131,13 +131,6 @@
: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)
+4 -8
View File
@@ -15,8 +15,6 @@
(def test-dir (.mkdtempSync fs test-dir-prefix))
(def initialized? (atom false))
(defn signal-received-callback
[a]
(re-frame/dispatch [:signals/signal-received a]))
@@ -24,12 +22,10 @@
;; We poll for signals, could not get callback working
(defn init!
[]
(when-not @initialized?
(.setSignalEventCallback native-status)
(reset! initialized? true)
(js/setInterval (fn []
(.pollSignal native-status signal-received-callback)
100))))
(.setSignalEventCallback native-status)
(js/setInterval (fn []
(.pollSignal native-status signal-received-callback)
100)))
(def status
(clj->js
@@ -1,32 +0,0 @@
(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"))))
@@ -1,17 +0,0 @@
(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})
@@ -1,25 +0,0 @@
(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))
@@ -1,15 +0,0 @@
(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})
@@ -1,102 +0,0 @@
(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))))
+2 -5
View File
@@ -78,16 +78,13 @@
(fn [can-save?]
(if can-save?
(keychain/get-credentials
(str key-uid "-hashed" "-auth")
(str key-uid "-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
:containstyle {:margin-top 8}}
{:type :error
:size :default
:icon :i/info
:style {:margin-top 8}}
(i18n/label :t/oops-wrong-password)])
[quo/button
{:container-style {:margin-bottom 12 :margin-top 40}
@@ -1,7 +0,0 @@
(ns status-im2.common.standard-authentication.core
(:require
status-im2.common.standard-authentication.standard-auth.button.view
status-im2.common.standard-authentication.standard-auth.slide-button.view))
(def button status-im2.common.standard-authentication.standard-auth.button.view/view)
(def slide-button status-im2.common.standard-authentication.standard-auth.slide-button.view/view)
@@ -1,44 +0,0 @@
(ns status-im2.common.standard-authentication.standard-auth.button.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
customization-color
auth-button-label
on-enter-password
on-auth-success
on-press
on-auth-fail
auth-button-icon-left
size
button-label
theme
blur?
container-style
icon-left]}]
[quo/button
{:size size
:container-style container-style
:customization-color customization-color
:icon-left icon-left
:on-press (if on-press
on-press
#(authorize/authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}))}
button-label])))
(def view (quo.theme/with-theme view-internal))
@@ -1,43 +0,0 @@
(ns status-im2.common.standard-authentication.standard-auth.slide-button.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
track-text
customization-color
auth-button-label
on-enter-password
on-auth-success
on-auth-fail
auth-button-icon-left
size
theme
blur?
container-style]}]
[rn/view {:style {:flex 1}}
[quo/slide-button
{:size size
:container-style container-style
:customization-color customization-color
:on-reset (when @reset-slider? #(reset! reset-slider? false))
:on-complete #(authorize/authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label})
:track-icon (if biometric-auth? :i/face-id :password)
:track-text track-text}]])))
(def view (quo.theme/with-theme view-internal))
@@ -1,6 +1,10 @@
(ns status-im2.common.standard-authentication.standard-auth.authorize
(ns status-im2.common.standard-authentication.standard-auth.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.touch-id :as biometric]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.enter-password.view :as enter-password]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
@@ -42,3 +46,39 @@
{:on-enter-password on-enter-password
:button-icon-left auth-button-icon-left
:button-label auth-button-label}])}]))))))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
track-text
customization-color
auth-button-label
on-enter-password
on-auth-success
on-auth-fail
auth-button-icon-left
size
theme
blur?
container-style]}]
[rn/view {:style {:flex 1}}
[quo/slide-button
{:size size
:container-style container-style
:customization-color customization-color
:on-reset (when @reset-slider? #(reset! reset-slider? false))
:on-complete #(authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label})
:track-icon (if biometric-auth? :i/face-id :password)
:track-text track-text}]])))
(def view (quo.theme/with-theme view-internal))
@@ -11,7 +11,7 @@
[utils.re-frame :as rf]))
(defn edit-message
[{:keys [text-value input-ref]}]
[state]
[rn/view
{:style style/container
:accessibility-label :edit-message}
@@ -30,18 +30,20 @@
{:size 24
:icon-only? true
:accessibility-label :edit-cancel-button
:on-press #(utils/cancel-edit-message text-value input-ref)
:on-press (fn []
(utils/cancel-edit-message state)
(rf/dispatch [:chat.ui/cancel-message-edit]))
:type :outline}
:i/close]])
(defn- f-view
[props]
[state]
(let [edit (rf/sub [:chats/edit-message])
height (reanimated/use-shared-value (if edit constants/edit-container-height 0))]
(rn/use-effect #(reanimated/animate height (if edit constants/edit-container-height 0)) [edit])
[reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})}
(when edit [edit-message props])]))
(when edit [edit-message state])]))
(defn view
[props]
[:f> f-view props])
[state]
[:f> f-view state])
@@ -6,7 +6,6 @@
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.keyboard :as kb]
[status-im2.contexts.chat.composer.utils :as utils]
@@ -106,48 +105,25 @@
(defn use-edit
[{:keys [input-ref]}
{:keys [text-value saved-cursor-position cursor-position]}
{:keys [edit input-with-mentions]}
{:keys [text-value saved-cursor-position]}
{:keys [edit]}
messages-list-on-layout-finished?]
(let [mention? (some #(= :mention (first %)) (seq input-with-mentions))
composer-just-opened? (not @messages-list-on-layout-finished?)]
(rn/use-effect
(fn []
(let [mention-text (reduce (fn [acc item]
(str acc (second item)))
""
input-with-mentions)
edit-text (cond
mention? mention-text
;; NOTE: using text-value for cases when the user
;; leaves the app with an unfinished edit and re-opens
;; the chat.
(and (seq @text-value) composer-just-opened?)
@text-value
:else (get-in edit [:content :text]))
selection-pos (count edit-text)
inject-edit-text (fn []
(reset! text-value edit-text)
(reset! cursor-position selection-pos)
(reset! saved-cursor-position selection-pos)
(when @input-ref
(.setNativeProps ^js @input-ref
(clj->js {:text edit-text}))))]
(when (and edit @input-ref)
;; NOTE: A small setTimeout is necessary to ensure the focus is enqueued and is executed
;; ASAP. Check https://github.com/software-mansion/react-native-screens/issues/472
;;
;; The nested setTimeout is necessary to avoid both `on-focus` and
;; `on-content-size-change` handlers triggering the height animation simultaneously, as
;; this causes a jump in the
;; UI. This way, `on-focus` will trigger first without changing the height, after which
;; `on-content-size-change` will animate the height of the input based on the injected
;; text.
(js/setTimeout #(do (when @messages-list-on-layout-finished? (.focus ^js @input-ref))
(reagent/next-tick inject-edit-text))
600))))
[(:message-id edit)])))
(rn/use-effect
(fn []
(let [edit-text (get-in edit [:content :text])
text-value-count (count @text-value)]
(when @messages-list-on-layout-finished?
;; A small setTimeout is necessary to ensure the statement is enqueued and will get
;; executed
;; ASAP. Https://github.com/software-mansion/react-native-screens/issues/472
(reset! saved-cursor-position (if edit-text
(count edit-text)
text-value-count))
(js/setTimeout #(.focus ^js @input-ref) 250))
(when (and edit @input-ref)
(.setNativeProps ^js @input-ref (clj->js {:text edit-text}))
(reset! text-value edit-text))))
[(:message-id edit)]))
(defn use-reply
[{:keys [input-ref]}
@@ -159,8 +135,25 @@
(when reply
(reanimated/animate container-opacity 1))
(when (and reply @input-ref @messages-list-on-layout-finished?)
(js/setTimeout #(.focus ^js @input-ref) 600)))
[(:message-id reply)]))
(js/setTimeout #(.focus ^js @input-ref) 250))))
[(:message-id reply)])
(defn edit-mentions
[{:keys [input-ref]} {:keys [text-value cursor-position]} {:keys [input-with-mentions]}]
(rn/use-effect (fn []
(let [input-text (reduce (fn [acc item]
(str acc (second item)))
""
input-with-mentions)]
(reset! text-value input-text)
(reset! cursor-position (count input-text))
(js/setTimeout #(when @input-ref
(.setNativeProps ^js @input-ref
(clj->js {:selection {:start (count input-text)
:end (count
input-text)}})))
300)))
[(some #(= :mention (first %)) (seq input-with-mentions))]))
(defn update-input-mention
[{:keys [input-ref]}
@@ -43,7 +43,8 @@
(reset! emoji-kb-extra-height nil))
(reset! maximized? false)
(rf/dispatch [:chat.ui/set-input-maximized false])
(utils/blur-input input-ref))
(when @input-ref
(.blur ^js @input-ref)))
(defn bounce-back
[{:keys [height saved-height opacity background-y]}
@@ -95,13 +96,13 @@
max-height
bounded-height
saved-height))
; sheet at min-height, collapse keyboard
(utils/blur-input input-ref))))))
(when @input-ref ; sheet at min-height, collapse keyboard
(.blur ^js @input-ref)))))))
(gesture/on-end (fn []
(let [diff (- (reanimated/get-shared-value height)
(reanimated/get-shared-value saved-height))]
(if @gesture-enabled?
(if (and @expanding? (>= diff 0))
(if (>= diff 0)
(if (> diff constants/drag-threshold)
(maximize state animations dimensions)
(bounce-back animations dimensions starting-opacity))
@@ -23,14 +23,12 @@
show-floating-scroll-down-button?]
(reset! focused? true)
(rf/dispatch [:chat.ui/set-input-focused true])
(let [last-height-value (reanimated/get-shared-value last-height)]
(reanimated/animate height last-height-value)
(reanimated/set-shared-value saved-height last-height-value)
(reanimated/animate container-opacity 1)
(when (> last-height-value (* constants/background-threshold max-height))
(reanimated/animate opacity 1)
(reanimated/set-shared-value background-y 0)))
(reanimated/animate height (reanimated/get-shared-value last-height))
(reanimated/set-shared-value saved-height (reanimated/get-shared-value last-height))
(reanimated/animate container-opacity 1)
(when (> (reanimated/get-shared-value last-height) (* constants/background-threshold max-height))
(reanimated/animate opacity 1)
(reanimated/set-shared-value background-y 0))
(js/setTimeout #(reset! lock-selection? false) 300)
(when (and (not-empty @text-value) @input-ref)
(.setNativeProps ^js @input-ref
@@ -74,7 +72,7 @@
"Save new text height, expand composer if possible, show background overlay if needed"
[event
{:keys [maximized? lock-layout? text-value]}
{:keys [height saved-height last-height opacity background-y]}
{:keys [height saved-height opacity background-y]}
{:keys [content-height window-height max-height]}
keyboard-shown]
(when keyboard-shown
@@ -89,9 +87,8 @@
max-height)
new-height (min new-height max-height)]
(reset! content-height content-size)
(when (utils/update-height? content-size height max-height)
(when (utils/update-height? content-size height max-height maximized?)
(reanimated/animate height new-height)
(reanimated/set-shared-value last-height new-height)
(reanimated/set-shared-value saved-height new-height))
(when (= new-height max-height)
(reset! maximized? true)
@@ -4,8 +4,7 @@
[react-native.async-storage :as async-storage]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im2.contexts.chat.composer.utils :as utils]))
[react-native.reanimated :as reanimated]))
(defn get-kb-height
[curr-height default-height]
@@ -21,24 +20,18 @@
(async-storage/set-item! :kb-default-height (str height)))))
(defn handle-emoji-kb-ios
"Opening emoji KB on iOS will cause a flicker up and down due to height differences.
This method handles that by adding the extra difference between the keyboards. When the input is
expanded to a point where the added difference will make the composer go beyond the screen causing a flicker,
we're subtracting the difference so it only reaches the allowed max-height. We're not animating these
changes to make it appear seamless during transitions between keyboard types when maximized."
"Opening emoji KB on iOS while maximized will cause a flicker up and down. This method handles that."
[event
{:keys [emoji-kb-extra-height]}
{:keys [text-value kb-height]}
{:keys [text-value]}
{:keys [height saved-height]}
{:keys [max-height]}]
(let [start-h (oops/oget event "startCoordinates.height")
end-h (oops/oget event "endCoordinates.height")
diff (- end-h start-h)
max-height-diff (- max-height diff)
curr-text @text-value
bigger-than-default-kb? (> end-h @kb-height)
almost-expanded? (> (reanimated/get-shared-value height) max-height-diff)]
(if (and almost-expanded? bigger-than-default-kb? (pos? diff))
(let [start-h (oops/oget event "startCoordinates.height")
end-h (oops/oget event "endCoordinates.height")
diff (- end-h start-h)
max-height-diff (- max-height diff)
curr-text @text-value]
(if (> (reanimated/get-shared-value height) max-height-diff)
(do
(reanimated/set-shared-value height (- (reanimated/get-shared-value height) diff))
(reanimated/set-shared-value saved-height (- (reanimated/get-shared-value saved-height) diff))
@@ -65,8 +58,8 @@
#(handle-emoji-kb-ios % props state animations dimensions)))
(reset! keyboard-hide-listener (.addListener rn/keyboard
"keyboardDidHide"
#(when platform/android?
(utils/blur-input input-ref)))))
#(when (and platform/android? @input-ref)
(.blur ^js @input-ref)))))
(defn handle-refocus-emoji-kb-ios
[{:keys [saved-emoji-kb-extra-height]}
@@ -9,7 +9,6 @@
[status-im2.constants :as constant]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.reply.style :as style]
[status-im2.contexts.chat.composer.utils :as utils]
[utils.ens.stateofus :as stateofus]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -86,7 +85,7 @@
(defn quoted-message
[{:keys [from content-type contentType parsed-text content deleted? deleted-for-me?
album-images-count]}
in-chat-input? pin? recording-audio? input-ref]
in-chat-input? pin? recording-audio?]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
current-public-key (rf/sub [:multiaccount/public-key])
content-type (or content-type contentType)
@@ -137,7 +136,7 @@
{:icon-only? true
:size 24
:accessibility-label :reply-cancel-button
:on-press #(utils/cancel-reply-message input-ref)
:on-press #(rf/dispatch [:chat.ui/cancel-message-reply])
:type :outline}
:i/close])
(when (and in-chat-input? recording-audio?)
@@ -149,13 +148,13 @@
:style style/gradient}])]))
(defn- f-view
[recording? input-ref]
[recording?]
(let [reply (rf/sub [:chats/reply-message])
height (reanimated/use-shared-value (if reply constants/reply-container-height 0))]
(rn/use-effect #(reanimated/animate height (if reply constants/reply-container-height 0)) [reply])
[reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})}
(when reply [quoted-message reply true false recording? input-ref])]))
(when reply [quoted-message reply true false recording?])]))
(defn view
[{:keys [recording?]} input-ref]
[:f> f-view @recording? input-ref])
[{:keys [recording?]}]
[:f> f-view @recording?])
@@ -16,10 +16,11 @@
(max min-v (min v max-v)))
(defn update-height?
[content-size height max-height]
(let [diff (Math/abs (- content-size (reanimated/get-shared-value height)))]
(and (not= (reanimated/get-shared-value height) max-height)
(> diff constants/content-change-threshold))))
[content-size height max-height maximized?]
(when-not @maximized?
(let [diff (Math/abs (- content-size (reanimated/get-shared-value height)))]
(and (not= (reanimated/get-shared-value height) max-height)
(> diff constants/content-change-threshold)))))
(defn show-top-gradient?
[y lines max-lines gradient-opacity focused?]
@@ -99,28 +100,10 @@
(not reply?)
(not audio?)))
(defn blur-input
[input-ref]
(when @input-ref
(rf/dispatch [:chat.ui/set-input-focused false])
(.blur ^js @input-ref)))
(defn cancel-reply-message
[input-ref]
(js/setTimeout #(blur-input input-ref) 100)
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height])
(rf/dispatch [:chat.ui/cancel-message-reply]))
(defn cancel-edit-message
[text-value input-ref]
[{:keys [text-value]}]
(reset! text-value "")
;; NOTE: adding a timeout to assure the input is blurred on the next tick
;; after the `text-value` was cleared. Otherwise the height will be calculated
;; with the old `text-value`, leading to wrong composer height after blur.
(js/setTimeout #(blur-input input-ref) 100)
(.setNativeProps ^js @input-ref (clj->js {:text ""}))
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height])
(rf/dispatch [:chat.ui/cancel-message-edit]))
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height]))
(defn count-lines
[s]
@@ -77,6 +77,7 @@
(effects/use-edit props state subscriptions messages-list-on-layout-finished?)
(effects/use-reply props animations subscriptions messages-list-on-layout-finished?)
(effects/update-input-mention props state subscriptions)
(effects/edit-mentions props state subscriptions)
(effects/link-previews props state animations subscriptions)
(effects/use-images props state animations subscriptions)
[:<>
@@ -98,10 +99,8 @@
[sub-view/bar]
(when chat-screen-loaded?
[:<>
[reply/view state (:input-ref props)]
[edit/view
{:text-value (:text-value state)
:input-ref (:input-ref props)}]])
[reply/view state]
[edit/view state]])
[reanimated/touchable-opacity
{:active-opacity 1
:on-press (fn []
@@ -154,8 +153,7 @@
(let [window-height (:height (rn/get-window))
theme (quo.theme/use-theme-value)
opacity (reanimated/use-shared-value 0)
background-y (reanimated/use-shared-value (- window-height)) ; Y position of background
; overlay
background-y (reanimated/use-shared-value (- window-height)) ; Y position of background overlay
blur-height (reanimated/use-shared-value (+ constants/composer-default-height
(:bottom insets)))
extra-params {:insets insets
@@ -412,15 +412,7 @@
;;TODO(rasom) https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:on-layout (fn [e]
;; FIXME: the 1s timeout is to assure all effects with
;; timeouts that depend on the value are considered.
;; Hacky, but we're heavily relying on timeouts in the
;; composer and need to react to differently (e.g.
;; inside effects/use-edit) when the chat has just
;; opened and the subsequent times.
(js/setTimeout #(reset! messages-list-on-layout-finished?
true)
1000)
(reset! messages-list-on-layout-finished? true)
(let [layout-height (oops/oget e
"nativeEvent.layout.height")]
(reset! messages-view-height layout-height)))
@@ -42,31 +42,26 @@
community-id %])
:on-error #(log/error "failed to request to join community" community-id %)}]})
(defn request-to-join
[{:keys [db]} [{:keys [community-id password]}]]
(let [pub-key (get-in db [:profile/profile :public-key])
addresses-to-reveal []]
{:fx [[:json-rpc/call
[{:method "wakuext_generateJoiningCommunityRequestsForSigning"
:params [pub-key community-id addresses-to-reveal]
:on-success [:communities/sign-data community-id password]
:on-error [:communities/requested-to-join-error community-id]}]]]}))
;; Event to be called to request to join a community.
;; This event will generate the data to be signed and then call the sign-data event.
;; This is the only event that should be called from the UI.
(rf/reg-event-fx :communities/request-to-join request-to-join)
(rf/reg-event-fx :communities/request-to-join
(fn [{:keys [db]} [{:keys [community-id password]}]]
(let [pub-key (get-in db [:profile/profile :public-key])
addresses-to-reveal []]
{:fx [[:json-rpc/call
[{:method "wakuext_generateJoiningCommunityRequestsForSigning"
:params [pub-key community-id addresses-to-reveal]
:on-success [:communities/sign-data community-id password]
:on-error [:communities/requested-to-join-error community-id]}]]]})))
(defn sign-data
[_ [community-id password sign-params]]
(let [addresses-to-reveal (map :account sign-params)]
{:fx [[:json-rpc/call
[{:method "wakuext_signData"
:params [(map #(assoc % :password password) sign-params)]
:on-success [:communities/request-to-join-with-signatures community-id addresses-to-reveal]
:on-error [:communities/requested-to-join-error community-id]}]]]}))
(rf/reg-event-fx :communities/sign-data sign-data)
(rf/reg-event-fx :communities/sign-data
(fn [_ [community-id password sign-params]]
{:fx [[:json-rpc/call
[{:method "wakuext_signData"
:params [(map #(assoc % :password password) sign-params)]
:on-success [:communities/request-to-join-with-signatures community-id]
:on-error [:communities/requested-to-join-error community-id]}]]]}))
(rf/reg-event-fx :communities/requested-to-join-error
(fn [{:keys [db]} [community-id error]]
@@ -76,19 +71,11 @@
:event :communities/requested-to-join-error})
{:db (assoc-in db [:password-authentication :error] error)}))
(defn request-to-join-with-signatures
[_ [community-id addresses-to-reveal signatures]]
{:fx [[:json-rpc/call
[{:method "wakuext_requestToJoinCommunity"
:params [{:communityId community-id
:signatures signatures
:addressesToReveal addresses-to-reveal
;; NOTE: At least one airdrop address is required.
;; This is a temporary solution while the address
;; selection feature is not implemented in mobile.
:airdropAddress (first addresses-to-reveal)}]
:js-response true
:on-success [:communities/requested-to-join]
:on-error [:communities/requested-to-join-error community-id]}]]]})
(rf/reg-event-fx :communities/request-to-join-with-signatures request-to-join-with-signatures)
(rf/reg-event-fx :communities/request-to-join-with-signatures
(fn [_ [community-id signatures]]
{:fx [[:json-rpc/call
[{:method "wakuext_requestToJoinCommunity"
:params [{:communityId community-id :signatures signatures}]
:js-response true
:on-success [:communities/requested-to-join]
:on-error [:communities/requested-to-join-error community-id]}]]]}))
@@ -1,53 +0,0 @@
(ns status-im2.contexts.communities.overview.events-test
(:require [cljs.test :refer [deftest is]]
[native-module.core :as native-module]
[status-im2.contexts.communities.overview.events :as sut]))
(def password (native-module/sha3 "password123"))
(def community-id "0x99")
(def account-pub-key "0x1")
(deftest request-to-join-test
(let [cofx {:db {:profile/profile {:public-key account-pub-key}}}
expected {:fx [[:json-rpc/call
[{:method "wakuext_generateJoiningCommunityRequestsForSigning"
:params [account-pub-key community-id []]
:on-success [:communities/sign-data community-id password]
:on-error [:communities/requested-to-join-error community-id]}]]]}]
(is (= expected
(sut/request-to-join cofx
[{:community-id community-id
:password password}])))))
(deftest sign-data-test
(let [cofx {:db {}}
sign-params [{:data "123" :account account-pub-key}
{:data "456" :account "0x2"}]
addresses-to-reveal [account-pub-key "0x2"]
expected {:fx
[[:json-rpc/call
[{:method "wakuext_signData"
:params [[{:data "123" :account account-pub-key :password password}
{:data "456" :account "0x2" :password password}]]
:on-success [:communities/request-to-join-with-signatures
community-id addresses-to-reveal]
:on-error [:communities/requested-to-join-error community-id]}]]]}]
(is (= expected
(sut/sign-data cofx [community-id password sign-params])))))
(deftest request-to-join-with-signatures-test
(let [cofx {:db {}}
addresses-to-reveal [account-pub-key "0x2"]
signatures ["11111" "222222"]
expected {:fx [[:json-rpc/call
[{:method "wakuext_requestToJoinCommunity"
:params [{:communityId community-id
:signatures signatures
:addressesToReveal addresses-to-reveal
:airdropAddress "0x1"}]
:js-response true
:on-success [:communities/requested-to-join]
:on-error [:communities/requested-to-join-error
community-id]}]]]}]
(is (= expected
(sut/request-to-join-with-signatures cofx [community-id addresses-to-reveal signatures])))))
@@ -255,7 +255,7 @@
(defn community-content
[community]
(rf/dispatch [:communities/check-all-community-channels-permissions (:id community)])
(fn [{:keys [name description joined images tags color id token-permissions] :as community}
(fn [{:keys [name description joined images tags color id] :as community}
pending?
{:keys [on-category-layout
collapsed?
@@ -273,14 +273,12 @@
:last-item-style style/last-community-tag
:container-style style/community-tag-container}])
[join-community community pending?]]
(when (or (and (seq token-permissions) joined)
(empty? token-permissions))
[channel-list-component
{:on-category-layout on-category-layout
:community-id id
:community-color color
:on-first-channel-height-changed on-first-channel-height-changed}
(add-handlers-to-categorized-chats id chats-by-category joined)])])))
[channel-list-component
{:on-category-layout on-category-layout
:community-id id
:community-color color
:on-first-channel-height-changed on-first-channel-height-changed}
(add-handlers-to-categorized-chats id chats-by-category joined)]])))
(defn sticky-category-header
[_]
@@ -1,20 +1,15 @@
(ns status-im2.contexts.onboarding.enable-biometrics.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im2.common.biometric.events :as biometric]
[status-im2.common.parallax.view :as parallax]
[status-im2.common.parallax.whitelist :as whitelist]
[status-im2.common.resources :as resources]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.contexts.onboarding.enable-biometrics.style :as style]
[status-im2.navigation.state :as state]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
[utils.re-frame :as rf]))
(defn page-title
[]
@@ -26,29 +21,22 @@
:description-accessibility-label :enable-biometrics-sub-title}])
(defn enable-biometrics-buttons
[insets theme]
[insets]
(let [supported-biometric-type (rf/sub [:biometric/supported-type])
bio-type-label (biometric/get-label-by-type supported-biometric-type)
profile-color (or (:color (rf/sub [:onboarding-2/profile]))
(rf/sub [:profile/customization-color]))
syncing-results? (= :syncing-results @state/root-id)]
profile-color (:color (rf/sub [:onboarding-2/profile]))]
[rn/view {:style (style/buttons insets)}
[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})}
{:on-press #(rf/dispatch [:onboarding-2/sync-enable-biometrics])})]
{:accessibility-label :enable-biometrics-button
:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])
:icon-left :i/face-id
:customization-color profile-color}
(i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]
[quo/button
{:accessibility-label :maybe-later-button
:background :blur
:type :grey
:on-press #(rf/dispatch (if syncing-results?
[:navigate-to-within-stack
[:enable-notifications :enable-biometrics]]
[:onboarding-2/create-account-and-login]))
:on-press #(rf/dispatch [:onboarding-2/create-account-and-login])
:container-style {:margin-top 12}}
(i18n/label :t/maybe-later)]]))
@@ -67,19 +55,12 @@
:style (style/page-illustration width)
:source (resources/get-image :biometrics)}]))
(defn f-enable-biometrics
[{:keys [theme]}]
(defn enable-biometrics
[]
(let [insets (safe-area/get-insets)]
[rn/view {:style (style/page-container insets)}
[page-title]
(if whitelist/whitelisted?
[enable-biometrics-parallax]
[enable-biometrics-simple])
[enable-biometrics-buttons insets theme]]))
(defn- internale-enable-biometrics
[params]
[:f> f-enable-biometrics params])
(def view (quo.theme/with-theme internale-enable-biometrics))
[enable-biometrics-buttons insets]]))
@@ -51,7 +51,7 @@
[quo/page-nav
{:background :blur
:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back-within-stack :enable-biometrics])}]
:on-press #(rf/dispatch [:navigate-back-within-stack :new-to-status])}]
[page-title]
[rn/view {:style style/page-illustration}
[quo/text
+5 -35
View File
@@ -35,29 +35,11 @@
{:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
(rf/defn sync-enable-biometrics
{:events [:onboarding-2/sync-enable-biometrics]}
[{:keys [db]} password]
{:db (-> db
(assoc-in [:onboarding-2/profile :syncing?] true))
:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
(rf/defn navigate-to-enable-notifications
{:events [:onboarding-2/navigate-to-enable-notifications]}
[{:keys [db]}]
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:db (dissoc db :onboarding-2/profile)
:dispatch [:navigate-to-within-stack [:enable-notifications :enable-biometrics]]}))
(rf/defn biometrics-done
{:events [:onboarding-2/biometrics-done]}
[{:keys [db]}]
(let [syncing? (get-in db [:onboarding-2/profile :syncing?])]
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
:dispatch (if syncing?
[:onboarding-2/finalize-setup]
[:onboarding-2/create-account-and-login])}))
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
:dispatch [:onboarding-2/create-account-and-login]})
(rf/defn biometrics-fail
{:events [:onboarding-2/biometrics-fail]}
@@ -101,14 +83,6 @@
[:navigate-to-within-stack [:enable-biometrics :new-to-status]]
[:onboarding-2/create-account-and-login])}))
(rf/defn navigate-to-enable-biometrics
{:events [:onboarding-2/navigate-to-enable-biometrics]}
[{:keys [db]}]
(let [supported-type (:biometric/supported-type db)]
{:dispatch (if supported-type
[:open-modal :enable-biometrics]
[:open-modal :enable-notifications])}))
(rf/defn seed-phrase-entered
{:events [:onboarding-2/seed-phrase-entered]}
[_ seed-phrase on-error]
@@ -144,20 +118,16 @@
(rf/defn onboarding-new-account-finalize-setup
{:events [:onboarding-2/finalize-setup]}
[{:keys [db]}]
(let [syncing? (get-in db [:onboarding-2/profile :syncing?])
masked-password (get-in db [:onboarding-2/profile :password])
(let [masked-password (get-in db [:onboarding-2/profile :password])
key-uid (get-in db [:profile/profile :key-uid])
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 (if syncing? (str key-uid "-hashed") key-uid)
{:key-uid key-uid
:masked-password masked-password
:on-success (fn []
(if syncing?
(rf/dispatch [:onboarding-2/navigate-to-enable-notifications])
(log/error "successfully saved biometrics")))
:on-success #(log/debug "successfully saved biometric")
:on-error #(log/error "failed to save biometrics"
{:key-uid key-uid
:error %})}))))
@@ -54,7 +54,7 @@
{:on-press (fn []
(when on-press
(on-press))
(rf/dispatch [:onboarding-2/navigate-to-enable-biometrics]))
(rf/dispatch [:open-modal :enable-notifications]))
:accessibility-label :continue-button
:customization-color profile-color
:container-style style/continue-button}
@@ -42,19 +42,11 @@
{: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])]
{:db (assoc-in db [:onboarding-2/profile :password] (security/mask-data password))
::login [key-uid password]}))
{::login [key-uid password]}))
(rf/defn redirect-to-root
[{:keys [db] :as cofx}]
@@ -166,24 +158,13 @@
{:on-success #(rf/dispatch [:profile.login/biometric-success])
:on-faile #(rf/dispatch [:profile.login/biometric-auth-fail])}})))
;; 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 %])))))
key-uid
#(rf/dispatch [:profile.login/get-user-password-success %]))))
;; result of :keychain/get-auth-method above
(rf/defn get-user-password-success
@@ -194,7 +175,7 @@
cofx
{:db (assoc-in db [:profile/login :password] password)}
(navigation/init-root :progress)
(biometry-login))))
(login))))
(rf/defn biometric-auth-fail
{:events [:profile.login/biometric-auth-fail]}
@@ -6,14 +6,14 @@
(defn- view-internal
[{:keys [theme title]}]
[{:keys [theme]}]
(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 title
:title "quo components preview"
: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 (dissoc @state :value) (:value @state)]])))
[quo/step @state (:value @state)]])))
@@ -487,7 +487,7 @@
(defn- main-screen
[]
[:<>
[common/navigation-bar {:title "Quo components preview"}]
[common/navigation-bar]
[rn/scroll-view {:style (style/main)}
(for [category (sort screens-categories)]
^{:key (first category)}
@@ -317,10 +317,10 @@
children)])
(defn- f-preview-container
[{:keys [title state descriptor blur? blur-dark-only?
[{:keys [state descriptor blur? blur-dark-only?
component-container-style
blur-container-style blur-view-props blur-height show-blur-background?]
:or {blur-height 200 title "quo component"}}
:or {blur-height 200}}
& 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 {:title title}]
[common/navigation-bar]
[rn/scroll-view
{:style (style/panel-basic)
:shows-vertical-scroll-indicator false}
@@ -1,22 +0,0 @@
(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)})
@@ -1,55 +0,0 @@
(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"]]]])))
@@ -1,59 +0,0 @@
(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}])
@@ -1,10 +0,0 @@
(ns status-im2.contexts.status-im-preview.style
(:require
[quo.foundations.colors :as colors]))
(defn main
[]
{:flex 1
:padding-bottom 8
:padding-horizontal 16
:background-color (colors/theme-colors colors/white colors/neutral-90)})
@@ -8,7 +8,7 @@
[reagent.core :as reagent]
[status-im2.common.qr-codes.view :as qr-codes]
[status-im2.common.resources :as resources]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.common.standard-authentication.standard-auth.view :as standard-auth]
[status-im2.contexts.syncing.setup-syncing.style :as style]
[status-im2.contexts.syncing.utils :as sync-utils]
[utils.datetime :as datetime]
@@ -119,7 +119,7 @@
(i18n/label :t/copy-qr)]])
(when-not (sync-utils/valid-connection-string? @code)
[rn/view {:style style/standard-auth}
[standard-auth/slide-button
[standard-auth/view
{:blur? true
:size :size-40
:track-text (i18n/label :t/slide-to-reveal-code)
@@ -1,38 +0,0 @@
(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,7 +3,18 @@
(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,6 +3,7 @@
[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]
@@ -13,12 +14,13 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
(defn- view-internal
[]
(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 %)
@@ -38,9 +40,8 @@
: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/add-watched-address
:bottom-action-label :t/create-account
:bottom-action-props {:customization-color @account-color
:disabled? (string/blank? @account-name)
:on-press #(re-frame/dispatch [:navigate-to
@@ -50,9 +51,11 @@
:right-icon :i/advanced
:icon-right? true
:emoji @account-emoji
:title (i18n/label :t/watched-address)
:title address-title
: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,24 +5,13 @@
: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,114 +2,51 @@
(: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 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
(defn view-internal
[]
(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]))
(let [input-value (reagent/atom "")
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 (fn []
(rf/dispatch [:wallet/clean-scanned-address])
(rf/dispatch [:navigate-back]))}]
:on-press #(rf/dispatch [:navigate-back])}]
[quo/text-combinations
{:container-style style/header-container
:title (i18n/label :t/add-address)
:description (i18n/label :t/enter-eth)}]
[: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])
[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]]
[quo/button
{:customization-color customization-color
:disabled? (string/blank? @input-value)
:on-press #(rf/dispatch [:navigate-to
:confirm-address-to-watch
{:address @input-value}])
:disabled? (clojure.string/blank? @input-value)
:on-press #(re-frame/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,5 +1,6 @@
(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]
@@ -7,12 +8,12 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
(defn- view-internal
[{: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 watch-only?]} & children]
custom-bottom-action]} & children]
(let [{:keys [top bottom]} (safe-area/get-insets)
margin-top (if (false? margin-top?) 0 top)
{window-width :width} (rn/get-window)]
@@ -35,7 +36,7 @@
{:customization-color account-color
:size 80
:emoji account-emoji
:type (if watch-only? :watch-only :default)}]
:type :default}]
[quo/button
{:size 32
:type :grey
@@ -80,3 +81,5 @@
:type :primary}
bottom-action-props)
(i18n/label bottom-action-label)])])]))
(def view (quo.theme/with-theme view-internal))
@@ -1,16 +1,13 @@
(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-internal
[{:keys [theme]}]
(defn view
[]
(let [{:keys [name color emoji address]} (rf/sub [:wallet/current-viewing-account])]
[:<>
[quo/drawer-top
@@ -29,13 +26,7 @@
:on-press #(rf/dispatch [:navigate-to :wallet-edit-account])}
{:icon :i/copy
:accessibility-label :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))}
:label (i18n/label :t/copy-address)}
{:icon :i/share
:accessibility-label :share-account
:label (i18n/label :t/share-account)}
@@ -51,5 +42,3 @@
{: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,5 +0,0 @@
(ns status-im2.contexts.wallet.common.validation
(:require [status-im2.constants :as constants]))
(defn ens-name? [s] (re-find constants/regx-ens s))
(defn eth-address? [s] (re-find constants/regx-address s))
@@ -7,7 +7,7 @@
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.common.standard-authentication.standard-auth.view :as standard-auth]
[status-im2.constants :as constants]
[status-im2.contexts.emoji-picker.utils :as emoji-picker.utils]
[status-im2.contexts.wallet.common.utils :as utils]
@@ -114,7 +114,7 @@
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data primary-name @derivation-path @account-color)}]
[standard-auth/slide-button
[standard-auth/view
{:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color
@@ -48,7 +48,7 @@
(fn []
(let [scanned-address (rf/sub [:wallet/scanned-address])
send-address (rf/sub [:wallet/send-address])
valid-ens-or-address? (rf/sub [:wallet/valid-ens-or-address?])]
valid-ens-or-address? (boolean (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,7 +1,5 @@
(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-test
(ns status-im2.integration-test.chat
(: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-test
(ns status-im2.integration-test.community
(: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-test
(ns status-im2.integration-test.core
(:require
[cljs.test :refer [deftest]]
[day8.re-frame.test :as rf-test]
@@ -8,14 +8,17 @@
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-test
(ns status-im2.integration-test.wallet
(:require
[cljs.test :refer [is]]
[cljs.test :refer [deftest is]]
[clojure.string :as string]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
@@ -12,9 +12,7 @@
status-im2.subs.root
[test-helpers.integration :as h]))
;; Workaround to skip test. Switch to `deftest` when test is fixed.
(defn create-wallet-account-test
[]
(deftest create-wallet-account-test
(h/log-headline :create-wallet-account-test)
(rf-test/run-test-async
(h/with-app-initialized
@@ -26,9 +24,7 @@
(h/logout)
(rf-test/wait-for [::logout/logout-method]))))))
;; Workaround to skip test. Switch to `deftest` when test is fixed.
(defn back-up-seed-phrase-test
[]
(deftest back-up-seed-phrase-test
(h/log-headline :back-up-seed-phrase-test)
(rf-test/run-test-async
(h/with-app-initialized
+9 -21
View File
@@ -32,7 +32,6 @@
[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]
@@ -47,7 +46,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-addresses.view :as wallet-saved-addresses]
[status-im2.contexts.wallet.saved-address.view :as wallet-saved-address]
[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]
@@ -162,15 +161,11 @@
:component create-password/create-password}
{:name :enable-biometrics
:options {:theme :dark
:layout options/onboarding-transparent-layout
:animations (merge transitions/new-to-status-modal-animations
transitions/push-animations-for-transparent-background)
:popGesture false
:modalPresentationStyle :overCurrentContext
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component enable-biometrics/view}
:options {:theme :dark
:layout options/onboarding-transparent-layout
:animations transitions/push-animations-for-transparent-background
:popGesture false}
:component enable-biometrics/enable-biometrics}
{:name :generating-keys
:options {:theme :dark
@@ -284,8 +279,8 @@
:options {:insets {:top? true}}
:component wallet-create-account/view}
{:name :wallet-saved-addresses
:component wallet-saved-addresses/view}
{:name :wallet-saved-address
:component wallet-saved-address/view}
{:name :wallet-select-address
:options {:modalPresentationStyle :overCurrentContext}
@@ -306,11 +301,4 @@
quo.preview/screens)
(when config/quo-preview-enabled?
quo.preview/main-screens)
(when config/quo-preview-enabled?
status-im-preview/screens)
(when config/quo-preview-enabled?
status-im-preview/main-screens)))
quo.preview/main-screens)))
+1 -4
View File
@@ -5,7 +5,6 @@
[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]
@@ -72,9 +71,7 @@
[bottom-sheet-screen/view {:content component}]
[component])]
(when js/goog.DEBUG
[:<>
[reloader/reload-view]
[schema.view/view]])]))))
[reloader/reload-view])]))))
(def bottom-sheet
(reagent/reactify-component
+7 -9
View File
@@ -2,7 +2,6 @@
(: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
@@ -46,11 +45,10 @@
:group-chats/extract-membership-signature
:utils/dispatch-later
:json-rpc/call})
(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)))))
(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))))
+1 -5
View File
@@ -2,9 +2,7 @@
(:require
[re-frame.core :as re-frame]
[react-native.core :as rn]
[reagent.core :as reagent]
schema.state
[status-im2.setup.schema :as schema]))
[reagent.core :as reagent]))
(defonce cnt (reagent/atom 0))
(defonce reload-locked? (atom false))
@@ -21,8 +19,6 @@
(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
@@ -1,104 +0,0 @@
(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
@@ -1,6 +0,0 @@
(ns status-im2.setup.schema-preload
":dev/always is needed so that the compiler doesn't cache this file."
{:dev/always true}
(:require [status-im2.setup.schema :as schema]))
(schema/setup!)
-8
View File
@@ -21,14 +21,6 @@
vals
(sort-by :position)))
(rf/reg-sub
:wallet/addresses
:<- [:wallet]
:-> #(->> %
:accounts
keys
set))
(rf/reg-sub
:wallet/balances
:<- [:wallet/accounts]
@@ -167,17 +167,3 @@
: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]
`(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)))))
`(if (app-initialized)
(do ~@body)
(do
(rf/dispatch [:app-started])
(rf-test/wait-for
[:profile/get-profiles-overview-success]
~@body))))
(defmacro with-account
[& body]
`(if (test-helpers.integration/messenger-started)
`(if (messenger-started)
(do ~@body)
(do
(test-helpers.integration/create-multiaccount!)
(rf-test/wait-for [:status-im.transport.core/messenger-started]
(test-helpers.integration/assert-messenger-started)
(create-multiaccount!)
(rf-test/wait-for
[:status-im.transport.core/messenger-started]
(assert-messenger-started)
~@body))))
-1
View File
@@ -1,5 +1,4 @@
(ns test-helpers.integration
(:require-macros [test-helpers.integration])
(:require
[cljs.test :refer [is]]
[native-module.core :as native-module]
-5
View File
@@ -2,7 +2,6 @@
(: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
@@ -238,7 +237,3 @@
:else
(str amount))))
(schema/=> format-amount
[:=> [:cat [:maybe :int]]
[:maybe :string]])
+60 -85
View File
@@ -1,7 +1,7 @@
import os
import re
import signal
import requests
import urllib.request
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime
@@ -161,57 +161,6 @@ 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
@@ -231,49 +180,75 @@ 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':
return
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)
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
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
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 _upload_time_limit(600):
class UploadApkException(Exception):
pass
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'
)
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 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
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
_upload_and_check_response_with_retries(apk_path)
if apk_src.startswith('http'):
os.remove(apk_path)
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)
def pytest_unconfigure(config):
@@ -159,13 +159,16 @@ 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.app'
# url_to_open = 'http://status.im' # ToDo: enable when a bug with Status link is fixed
url_to_open = 'https://coinmarketcap.com/'
self.chat_1.send_message(url_to_open)
chat_element = self.chat_2.chat_element_by_text(url_to_open)
if chat_element.is_element_displayed(120):
chat_element.click_on_link_inside_message_body()
web_view = self.chat_2.open_in_status_button.click()
if not web_view.element_by_text('Make the jump to web3').is_element_displayed(60):
# if not web_view.element_by_text('Private, Secure Communication').is_element_displayed(60):
if not web_view.element_by_text_part(
"Today's Cryptocurrency Prices by Market Cap").is_element_displayed(30):
self.errors.append('URL was not opened from 1-1 chat')
else:
self.errors.append("Message with URL was not received")
@@ -290,7 +293,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,8 +33,6 @@ 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()
@@ -192,6 +190,8 @@ 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,9 +200,10 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.errors.append(
"Incorrect contacts number restored: %s instead of %s" % (contacts_number, len(waku_user.contacts)))
else:
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)
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)
# 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)
@@ -247,7 +248,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.wait_and_click()
self.sign_in.show_profiles_button.click()
self.sign_in.element_by_text(self.username).click()
self.sign_in.sign_in()
self.home.communities_tab.click()
@@ -256,7 +257,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.wait_and_click()
self.sign_in.show_profiles_button.click()
user_card = self.sign_in.get_user(username=self.username)
user_card.open_user_options()
self.sign_in.remove_profile_button.click()
@@ -266,7 +267,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.wait_and_click()
self.sign_in.show_profiles_button.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...LSpUs', 'zQ3...xAZUU']
contacts = ['zQ3...rLSpUs', 'zQ3...mxAZUU']
blocked_user = 'Clear Flat Milkweedbug'
+1 -5
View File
@@ -1,10 +1,6 @@
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: [
'@testing-library/jest-native/extend-expect',
'../component-spec/status_im2.setup.schema_preload.js',
'../test/jest/jestSetup.js',
],
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect', '../test/jest/jestSetup.js'],
setupFiles: [],
testPathIgnorePatterns: [],
moduleNameMapper: {
-7
View File
@@ -25,13 +25,6 @@ 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', () => ({
+2 -7
View File
@@ -1565,8 +1565,7 @@
"delete-account": "Delete account",
"delete-keys-keycard": "Delete keys from Keycard",
"watch-only": "Watch-only",
"watched-address": "Watched address",
"add-watched-address": "Add watched address",
"watch-address": "Watch 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",
@@ -2385,9 +2384,5 @@
"address-activity": "This address has activity",
"keypairs": "Keypairs",
"keypairs-description": "Select keypair to derive your new account from",
"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"
"confirm-account-origin": "Confirm account origin"
}