Compare commits

..
Author SHA1 Message Date
Siddarth Kumar c5cbb79bc7 chore: commit wip stuff 2024-03-22 14:08:26 +05:30
304 changed files with 5227 additions and 6854 deletions
-1
View File
@@ -26,7 +26,6 @@
malli.generator malli.generator
malli.transform malli.transform
malli.util malli.util
promesa.core p
schema.core schema
status-im.feature-flags ff
taoensso.timbre log}}
-9
View File
@@ -1,9 +0,0 @@
{:lint-as {promesa.core/-> clojure.core/->
promesa.core/->> clojure.core/->>
promesa.core/as-> clojure.core/as->
promesa.core/let clojure.core/let
promesa.core/plet clojure.core/let
promesa.core/loop clojure.core/loop
promesa.core/recur clojure.core/recur
promesa.core/with-redefs clojure.core/with-redefs
promesa.core/doseq clojure.core/doseq}}
+28 -31
View File
@@ -22,37 +22,34 @@
:multi-lhs-hang]
:fn-map
{"reg-sub" :arg1-pair
"h/describe" :arg1-body
"h/describe-skip" :arg1-body
"h/describe-only" :arg1-body
"h/test" :arg1-body
"h/test-skip" :arg1-body
"h/test-only" :arg1-body
"test/async" :arg1-body
"test/use-fixtures" :arg1-body
"global.describe" :arg1-body
"global.test" :arg1-body
"list-comp" :binding
"defview" :arg1-body
"letsubs" :binding
"with-let" "let"
"reg-event-fx" :arg1-pair
"reg-fx" :arg1-pair
"testing" :arg1-body
"deftest-sub" :arg1-body
"test-async" :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?]]}]
"set!" "reset!"
"assoc-when" "assoc"
"assoc-some" "assoc"
"conj-when" "conj"
"conj-some" "conj"}
{"reg-sub" :arg1-pair
"h/describe" :arg1-body
"h/describe-skip" :arg1-body
"h/describe-only" :arg1-body
"h/test" :arg1-body
"h/test-skip" :arg1-body
"h/test-only" :arg1-body
"global.describe" :arg1-body
"global.test" :arg1-body
"list-comp" :binding
"defview" :arg1-body
"letsubs" :binding
"with-let" "let"
"reg-event-fx" :arg1-pair
"reg-fx" :arg1-pair
"testing" :arg1-body
"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?]]}]
"set!" "reset!"
"assoc-when" "assoc"
"assoc-some" "assoc"
"conj-when" "conj"
"conj-some" "conj"}
:style-map
{:no-comma {:map {:comma? false}}
+3 -5
View File
@@ -331,14 +331,13 @@ shadow-server:##@ Start shadow-cljs in server mode for watching
_test-clojure: export TARGET := clojure
_test-clojure: export WATCH ?= false
_test-clojure: status-go-library
_test-clojure:
ifeq ($(WATCH), true)
yarn node-pre-gyp rebuild && \
yarn install && \
yarn shadow-cljs compile mocks && \
nodemon --exec "yarn shadow-cljs compile test && node --require ./test-resources/override.js $$SHADOW_OUTPUT_TO" -e cljs
else
yarn node-pre-gyp rebuild && \
yarn install && \
yarn shadow-cljs compile mocks && \
yarn shadow-cljs compile test && \
node --require ./test-resources/override.js "$$SHADOW_OUTPUT_TO"
@@ -351,9 +350,8 @@ test: _test-clojure
test-watch-for-repl: export SHADOW_OUTPUT_TO := target/test/test.js
test-watch-for-repl: export SHADOW_NS_REGEXP := .*-test$$
test-watch-for-repl: status-go-library
test-watch-for-repl: ##@test Watch all Clojure tests and support REPL connections
yarn node-pre-gyp rebuild
yarn install
rm -f target/test/test.js
yarn shadow-cljs compile mocks && \
concurrently --kill-others --prefix-colors 'auto' --names 'build,repl' \
+1 -1
View File
@@ -1 +1 @@
2.28.0
1.25.0
+2
View File
@@ -74,6 +74,8 @@
</dict>
<key>NSCameraUsageDescription</key>
<string>Status uses camera to take pictures and scan QR codes.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Status needs access to the local network to give you the ability to connect with peers.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Location access is required for some DApps to function properly.</string>
<key>NSLocationWhenInUseUsageDescription</key>
@@ -4,10 +4,25 @@
#import "Statusgo.h"
#import "Utils.h"
@interface NetworkManager() <NSNetServiceBrowserDelegate>
@property (nonatomic, strong) NSNetServiceBrowser *serviceBrowser;
@property (nonatomic, copy) RCTResponseSenderBlock callback;
@end
@implementation NetworkManager
RCT_EXPORT_MODULE();
- (instancetype)init {
if ((self = [super init])) {
self.serviceBrowser = [[NSNetServiceBrowser alloc] init];
self.serviceBrowser.delegate = self;
}
return self;
}
RCT_EXPORT_METHOD(startSearchForLocalPairingPeers:(RCTResponseSenderBlock)callback) {
NSString *result = StatusgoStartSearchForLocalPairingPeers();
callback(@[result]);
@@ -95,6 +110,39 @@ RCT_EXPORT_METHOD(callPrivateRPC:(NSString *)payload
});
}
RCT_EXPORT_METHOD(requestLocalNetworkAccess:(RCTResponseSenderBlock)callback) {
self.callback = callback;
// Start browsing for services using TCP on the local network.
dispatch_async(dispatch_get_main_queue(), ^{
[self.serviceBrowser searchForServicesOfType:@"_http._tcp." inDomain:@""];
});
}
// NSNetServiceBrowser delegate method
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary<NSString *,NSNumber *> *)errorDict {
RCTLogInfo(@"Failed to search for services: %@", errorDict);
if (self.callback) {
self.callback(@[@"Failed to search for services"]);
}
}
// NSNetServiceBrowser delegate method
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
RCTLogInfo(@"Found service: %@", service);
// we stop the search after finding the first service or wait until the search is finished.
[browser stop];
if (self.callback) {
self.callback(@[[NSNull null], @"Requested local network access and found service"]);
}
}
// NSNetServiceBrowser delegate method
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser {
RCTLogInfo(@"Stopped searching for services");
}
#pragma mark - Recover
RCT_EXPORT_METHOD(recover:(NSString *)message
@@ -1888,36 +1888,6 @@ void _InitLogging(const FunctionCallbackInfo<Value>& args) {
delete c;
}
void _RestoreAccountAndLogin(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (args.Length() != 1) {
// Throw an Error that is passed back to JavaScript
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8Literal(isolate, "Wrong number of arguments for RestoreAccountAndLogin")));
return;
}
// Check the argument types
if (!args[0]->IsString()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'RestoreAccountAndLogin'")));
return;
}
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
char *arg0 = *arg0Obj;
// Call exported Go function, which returns a C string
char *c = RestoreAccountAndLogin(arg0);
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
args.GetReturnValue().Set(ret);
delete c;
}
void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "multiAccountGenerateAndDeriveAddresses", _MultiAccountGenerateAndDeriveAddresses);
@@ -1975,7 +1945,6 @@ void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "connectionChange", _ConnectionChange);
NODE_SET_METHOD(exports, "pollSignal", _PollSignal);
NODE_SET_METHOD(exports, "initLogging", _InitLogging);
NODE_SET_METHOD(exports, "restoreAccountAndLogin", _RestoreAccountAndLogin);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
+136 -154
View File
@@ -90,20 +90,20 @@
},
{
"path": "cider/cider-nrepl/0.31.0/cider-nrepl-0.31.0",
"path": "cider/cider-nrepl/0.25.3/cider-nrepl-0.25.3",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "1f28a4a834a7a46cf24def971b1a705a6795c73a",
"sha256": "0h4pyzy8rzzh567khvimw2cs8jr640kqxgaxvbfx1s5yfp56fka2"
"sha1": "5ae0efd9377a5e60c084bdaf4a2ce094f759ce23",
"sha256": "0drxf9nm23i1pcgrkwbcr09msq37csilzww38709add0hz8spjhq"
}
},
{
"path": "cider/piggieback/0.5.2/piggieback-0.5.2",
"path": "cider/piggieback/0.4.1/piggieback-0.4.1",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "ecfd5c286a85db3f059e75c37fca5722d9e26f79",
"sha256": "1ps9yf3cxmlm447hqkidjb5xry90n0wl3jk0jn28fagq31lzylkl"
"sha1": "0a02a3e2ecd7a126ab60d8a44793342f20ced79b",
"sha256": "142vl5np33akcrnn6pksi0rjfsmmi528villxsj6cwcndvybiw4m"
}
},
@@ -251,6 +251,15 @@
}
},
{
"path": "com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "c9ad4a0850ab676c5c64461a05ca524cdfff59f1",
"sha256": "170rflxnqnah0265ik2aylmxkshyqbf2zas9bp2l32xqj9l6jsaf"
}
},
{
"path": "com/google/errorprone/error_prone_annotations/2.15.0/error_prone_annotations-2.15.0",
"host": "https://repo1.maven.org/maven2",
@@ -297,11 +306,11 @@
},
{
"path": "com/google/javascript/closure-compiler-unshaded/v20230802/closure-compiler-unshaded-v20230802",
"path": "com/google/javascript/closure-compiler-unshaded/v20230411/closure-compiler-unshaded-v20230411",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "0a2498a4d45bf5896042f31da0cd30c5e7b415af",
"sha256": "0jvgqq0gnvm4jw2zac4677ds05klvsjalszdzcq3nw8g7fjyjv2x"
"sha1": "2f5d7ab921f9cc07ffeb4e1c0f156f164c650eeb",
"sha256": "0gphdrrhr88bcqa1scndachvhbayh7m11zm9hcsmvzn9bw72pabw"
}
},
@@ -323,51 +332,6 @@
}
},
{
"path": "com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "c9ad4a0850ab676c5c64461a05ca524cdfff59f1",
"sha256": "170rflxnqnah0265ik2aylmxkshyqbf2zas9bp2l32xqj9l6jsaf"
}
},
{
"path": "com/taoensso/encore/3.68.0/encore-3.68.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "b098b58e27a7cf9ab5e46aedbb5ababc12805170",
"sha256": "1i5bmm83y9wpgvl5h6ysjsw58qrqz4b4v5vqnsprq6f1pmakjfz2"
}
},
{
"path": "com/taoensso/timbre/6.3.1/timbre-6.3.1",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "e1b04be262f38bbd9af8f1193b6b3f2c6a37129d",
"sha256": "0p6rws5gpab5qldp739lyqvwdbak28vaxbjw2bg2slflkkahszc7"
}
},
{
"path": "com/taoensso/truss/1.11.0/truss-1.11.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "4784f43191d5839448b58128bb455d62616086fd",
"sha256": "16xqav4cv3cnfik2d8v3wn7zb2a97ybnrbp6v6nj8m1nmdhr16k7"
}
},
{
"path": "com/taoensso/tufte/2.6.3/tufte-2.6.3",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "8862c6113ca743ebdcfa17bf4779a21fc08a7c7e",
"sha256": "0i0wsv2g2hswgdik6zlgcs212clj1d7fvckxfcsmyfr0vvpc1dmd"
}
},
{
"path": "commons-codec/commons-codec/1.15/commons-codec-1.15",
"host": "https://repo1.maven.org/maven2",
@@ -404,6 +368,42 @@
}
},
{
"path": "com/taoensso/encore/3.68.0/encore-3.68.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "b098b58e27a7cf9ab5e46aedbb5ababc12805170",
"sha256": "1i5bmm83y9wpgvl5h6ysjsw58qrqz4b4v5vqnsprq6f1pmakjfz2"
}
},
{
"path": "com/taoensso/timbre/6.3.1/timbre-6.3.1",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "e1b04be262f38bbd9af8f1193b6b3f2c6a37129d",
"sha256": "0p6rws5gpab5qldp739lyqvwdbak28vaxbjw2bg2slflkkahszc7"
}
},
{
"path": "com/taoensso/truss/1.11.0/truss-1.11.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "4784f43191d5839448b58128bb455d62616086fd",
"sha256": "16xqav4cv3cnfik2d8v3wn7zb2a97ybnrbp6v6nj8m1nmdhr16k7"
}
},
{
"path": "com/taoensso/tufte/2.6.3/tufte-2.6.3",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "8862c6113ca743ebdcfa17bf4779a21fc08a7c7e",
"sha256": "0i0wsv2g2hswgdik6zlgcs212clj1d7fvckxfcsmyfr0vvpc1dmd"
}
},
{
"path": "crypto-equality/crypto-equality/1.0.1/crypto-equality-1.0.1",
"host": "https://repo.clojars.org",
@@ -449,15 +449,6 @@
}
},
{
"path": "funcool/promesa/11.0.678/promesa-11.0.678",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "0ee1690ba74b2609c2d85b51edacad13046a18e2",
"sha256": "1wif8nwdbpchwg56fz47ymjnflmva0fzy0bw1s6bacnac472pmza"
}
},
{
"path": "hiccup/hiccup/1.0.5/hiccup-1.0.5",
"host": "https://repo.clojars.org",
@@ -513,11 +504,11 @@
},
{
"path": "io/undertow/undertow-core/2.3.10.Final/undertow-core-2.3.10.Final",
"path": "io/undertow/undertow-core/2.2.4.Final/undertow-core-2.2.4.Final",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "96d1d5ca6406a75a285b95ea8f4b39e0fb2d707e",
"sha256": "1s1m5hfhmfr7jsp4bn4c6fhdbgiz72mz0a96494nyhfa0b4lql8l"
"sha1": "78650b4029dd9280c4769d9425b5559f12cb83bf",
"sha256": "1brpkd2l98byf76jga6hj5drv5sj2d93lxlq2xz0rxpqyqas1xm4"
}
},
@@ -603,11 +594,11 @@
},
{
"path": "nrepl/nrepl/0.9.0/nrepl-0.9.0",
"path": "nrepl/nrepl/1.0.0/nrepl-1.0.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "49beada131959f08f9a20899affbd1b10c03a668",
"sha256": "1phak0479s27ffw9wzz7pi8cqqs6ipsm15dhgw9szxxcfhx529qa"
"sha1": "f47774c43493efdc879d36b95ebd67ea0d9c890a",
"sha256": "1fx5ssmixgqmklliw0ng8fjz41kkhys56x8dbwv9yqrfzws9f2x3"
}
},
@@ -620,15 +611,6 @@
}
},
{
"path": "org/apache/ant/ant-launcher/1.10.11/ant-launcher-1.10.11",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "ea0a0475fb6dfcdcf48b30410fd9d4f5c80df07e",
"sha256": "0mnj5v660qvmrsi1m6z0dnykw3df8f1213byzp45l2wqgbgk1dfs"
}
},
{
"path": "org/apache/ant/ant/1.10.11/ant-1.10.11",
"host": "https://repo1.maven.org/maven2",
@@ -639,11 +621,11 @@
},
{
"path": "org/babashka/sci.impl.types/0.0.2/sci.impl.types-0.0.2",
"host": "https://repo.clojars.org",
"path": "org/apache/ant/ant-launcher/1.10.11/ant-launcher-1.10.11",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "45a05ece33609c3ad26a6ea4e05130560da82306",
"sha256": "0pwwqq11rcknpcwbwsbw7pgbgnd7hqiawn0r8yvk14qfwa6p7z46"
"sha1": "ea0a0475fb6dfcdcf48b30410fd9d4f5c80df07e",
"sha256": "0mnj5v660qvmrsi1m6z0dnykw3df8f1213byzp45l2wqgbgk1dfs"
}
},
@@ -656,6 +638,15 @@
}
},
{
"path": "org/babashka/sci.impl.types/0.0.2/sci.impl.types-0.0.2",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "45a05ece33609c3ad26a6ea4e05130560da82306",
"sha256": "0pwwqq11rcknpcwbwsbw7pgbgnd7hqiawn0r8yvk14qfwa6p7z46"
}
},
{
"path": "org/checkerframework/checker-qual/3.12.0/checker-qual-3.12.0",
"host": "https://repo1.maven.org/maven2",
@@ -746,15 +737,6 @@
}
},
{
"path": "org/clojure/google-closure-library-third-party/0.0-20230227-c7c0a541/google-closure-library-third-party-0.0-20230227-c7c0a541",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "f5ea82eb1309b81ada6a14371bb848323c65e38b",
"sha256": "0jk9v4bfrxvz6wq1s86msry2mf47nwcjfplnn41yabqc44g82hva"
}
},
{
"path": "org/clojure/google-closure-library/0.0-20230227-c7c0a541/google-closure-library-0.0-20230227-c7c0a541",
"host": "https://repo1.maven.org/maven2",
@@ -764,6 +746,15 @@
}
},
{
"path": "org/clojure/google-closure-library-third-party/0.0-20230227-c7c0a541/google-closure-library-third-party-0.0-20230227-c7c0a541",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "f5ea82eb1309b81ada6a14371bb848323c65e38b",
"sha256": "0jk9v4bfrxvz6wq1s86msry2mf47nwcjfplnn41yabqc44g82hva"
}
},
{
"path": "org/clojure/math.combinatorics/0.2.0/math.combinatorics-0.2.0",
"host": "https://repo1.maven.org/maven2",
@@ -791,15 +782,6 @@
}
},
{
"path": "org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "449691b55d7d526258ce02c69b4699f2897c494d",
"sha256": "0phfs1z1scvdi00348zjh223xncmgrkmlrnbca4dh7lk701gy34i"
}
},
{
"path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0",
"host": "https://repo1.maven.org/maven2",
@@ -809,6 +791,15 @@
}
},
{
"path": "org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "449691b55d7d526258ce02c69b4699f2897c494d",
"sha256": "0phfs1z1scvdi00348zjh223xncmgrkmlrnbca4dh7lk701gy34i"
}
},
{
"path": "org/clojure/tools.cli/1.0.206/tools.cli-1.0.206",
"host": "https://repo1.maven.org/maven2",
@@ -837,11 +828,11 @@
},
{
"path": "org/clojure/tools.reader/1.3.7/tools.reader-1.3.7",
"path": "org/clojure/tools.reader/1.3.6/tools.reader-1.3.6",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "c60dfd3d79cbc5ec6264ba45c14b8d4c9b6708b2",
"sha256": "1mr0qx3gw1myfls07vrx3arwsi5n95b2lwk74ijpj0z3lzpzlj7m"
"sha1": "927809dcb44fa726e4969d993e3e733636d95ebb",
"sha256": "1q5q7fmshybvp55f6qys8i5sbzfaix5v9f9b55dkbhv55hgv7l8i"
}
},
@@ -855,38 +846,38 @@
},
{
"path": "org/jboss/logging/jboss-logging/3.4.3.Final/jboss-logging-3.4.3.Final",
"path": "org/jboss/logging/jboss-logging/3.4.1.Final/jboss-logging-3.4.1.Final",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "c4bd7e12a745c0e7f6cf98c45cdcdf482fd827ea",
"sha256": "0jdcziqbdzmah981zv3q88k2zrnclr2h1k3h3vjn002m9p54qchb"
"sha1": "40fd4d696c55793e996d1ff3c475833f836c2498",
"sha256": "1f7f4kjn3v6vibhgsr3k8hl0r2xq0c2rbcl82dx0bqg5jdyqgzlf"
}
},
{
"path": "org/jboss/threads/jboss-threads/3.5.0.Final/jboss-threads-3.5.0.Final",
"path": "org/jboss/threads/jboss-threads/3.1.0.Final/jboss-threads-3.1.0.Final",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "dd23d4788b3eafe9597ef3fe028e46ceb293ba8d",
"sha256": "1cchp54dw1578my27siim3h5jbf240aq8q6xd3z2b1ajgxxbcl71"
"sha1": "9b260c0302f637a84a52d3d118214a3c59217615",
"sha256": "0yc0p71y7yjhqjhkvqqi9xqgljavp3aslnik2k7dppbxnkrnlff9"
}
},
{
"path": "org/jboss/xnio/xnio-api/3.8.8.Final/xnio-api-3.8.8.Final",
"path": "org/jboss/xnio/xnio-api/3.8.0.Final/xnio-api-3.8.0.Final",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "1ba9c8b9a8dea1c6cd656155943e6d4c2c631fa7",
"sha256": "0wvqdcbardi55l77aw5404qgw70384hw155xvc66shn7l6z8h6bh"
"sha1": "e2c29acf42ac6f42c34f0b74ba089e3f3d1b2394",
"sha256": "041qnvb74bkgmfxyd23vdl26il55cmfbhi6a4bm3fcmyrpapjfjl"
}
},
{
"path": "org/jboss/xnio/xnio-nio/3.8.8.Final/xnio-nio-3.8.8.Final",
"path": "org/jboss/xnio/xnio-nio/3.8.0.Final/xnio-nio-3.8.0.Final",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "a5d2faf8d02e0a0bb9e9eabca8e38173cb640331",
"sha256": "0pxw2iv1q1yzk39knc650rg4vflsyjzhf07mwm2s5aqn5h82sk3i"
"sha1": "90c57dcf7f8c846b4da331d0e6e048c39842c92a",
"sha256": "1zndnb5648wqw23gj80zjqk3yd8gq6grqhz9n4zrcqixx1z586jf"
}
},
@@ -918,20 +909,11 @@
},
{
"path": "org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9",
"path": "org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "7cf2726fdcfbc8610f9a71fb3ed639871f315340",
"sha256": "0v1b7iydpnh2rvyzqvqbqm19vr2qva8icq84690bppnpr06r6608"
}
},
{
"path": "org/slf4j/slf4j-nop/2.0.9/slf4j-nop-2.0.9",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "fa0627049304bf501bf6a0e4d1416bcaec9a72a9",
"sha256": "10vdg9qkailjbzmf5mfnv328675swmnf1x3g9v7ymhxs29xkc4jn"
"sha1": "6c62681a2f655b49963a5983b8b0950a6120ae14",
"sha256": "1h512ry8g0nriazg3dqzs6s96502a77drw8vq26nfya97rg5gvyk"
}
},
@@ -945,11 +927,11 @@
},
{
"path": "org/wildfly/common/wildfly-common/1.5.4.Final/wildfly-common-1.5.4.Final",
"path": "org/wildfly/common/wildfly-common/1.5.2.Final/wildfly-common-1.5.2.Final",
"host": "https://repo1.maven.org/maven2",
"jar": {
"sha1": "735ceee5616d5143bac1bc740e444697073c002f",
"sha256": "0sp48y3q7bmp3bjb7a6fpz8gb7ssizvxlw5wdv2xwa6mifpkrnlz"
"sha1": "8eba40cfe86bcfcc223551e75201e6e7574c7c36",
"sha256": "17rkkm9dqbxdzpamwl9jr8z285awsj6hzpavzmc87wcz9469hp5b"
}
},
@@ -962,6 +944,15 @@
}
},
{
"path": "reagent/reagent/1.2.0/reagent-1.2.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "1b9a181b5c7ed3557768d2ea0c66f5616aef5e97",
"sha256": "0scvkzfqjs613z10rngh7427v3pxdqablf0fcl65pbpkzz16wgav"
}
},
{
"path": "re-com/re-com/2.8.0/re-com-2.8.0",
"host": "https://repo.clojars.org",
@@ -971,6 +962,15 @@
}
},
{
"path": "refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "6bc3441afc94f7ca024e41a864ca75e05df7e207",
"sha256": "0w8hax99y98l53mixxzx2ja0vcnhjv8dnsaz1zj3vqk775ns5w6i"
}
},
{
"path": "re-frame/re-frame/1.3.0/re-frame-1.3.0",
"host": "https://repo.clojars.org",
@@ -998,24 +998,6 @@
}
},
{
"path": "reagent/reagent/1.2.0/reagent-1.2.0",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "1b9a181b5c7ed3557768d2ea0c66f5616aef5e97",
"sha256": "0scvkzfqjs613z10rngh7427v3pxdqablf0fcl65pbpkzz16wgav"
}
},
{
"path": "refactor-nrepl/refactor-nrepl/3.9.1/refactor-nrepl-3.9.1",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "f949af92dd1d6fef59d7447bd0cb62118e34eca1",
"sha256": "0dml9yvjmji6xk2sk3cdmkvvnh13rw29szxxl363hap8yg28xcs1"
}
},
{
"path": "ring-cors/ring-cors/0.1.8/ring-cors-0.1.8",
"host": "https://repo.clojars.org",
@@ -1053,11 +1035,11 @@
},
{
"path": "thheller/shadow-cljs/2.26.2/shadow-cljs-2.26.2-aot",
"path": "thheller/shadow-cljs/2.25.0/shadow-cljs-2.25.0-aot",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "ab6b31649cb03e352369698781207db4f6da6d5f",
"sha256": "0mh28nykpillkym7hvplrr7vy5hn6a2ymafyf4yfl5y2kfx4yq42"
"sha1": "013921db91ce4a3616aec9c72c1832a014a0fece",
"sha256": "0ks380z7h8i2ylirvjgmlicq9jjpz9w71gjv521h4xs5fb273cl0"
}
},
@@ -1071,11 +1053,11 @@
},
{
"path": "thheller/shadow-undertow/0.3.3/shadow-undertow-0.3.3",
"path": "thheller/shadow-undertow/0.3.1/shadow-undertow-0.3.1",
"host": "https://repo.clojars.org",
"jar": {
"sha1": "ba377c3b70fd942b3ca7e38f055412a441626711",
"sha256": "10w2slj4znm93j0qq3ldj9hj0i7a50vbnmn6kv8i77fakikdjc48"
"sha1": "9be444bea4037bb80b451cc52a8e80359c4c45be",
"sha256": "174s2rdxvp7d4jg9kvzjadps42bdsbi05rs2pjy5i0ssq9n23zwa"
}
},
+25 -27
View File
@@ -8,8 +8,8 @@ borkdude/edamame/1.3.23/edamame-1.3.23.jar
borkdude/sci.impl.reflector/0.0.1/sci.impl.reflector-0.0.1.jar
camel-snake-kebab/camel-snake-kebab/0.4.3/camel-snake-kebab-0.4.3.jar
cheshire/cheshire/5.11.0/cheshire-5.11.0.jar
cider/cider-nrepl/0.31.0/cider-nrepl-0.31.0.jar
cider/piggieback/0.5.2/piggieback-0.5.2.jar
cider/cider-nrepl/0.25.3/cider-nrepl-0.25.3.jar
cider/piggieback/0.4.1/piggieback-0.4.1.jar
clj-kondo/clj-kondo/2023.09.07/clj-kondo-2023.09.07.jar
cljs-bean/cljs-bean/1.9.0/cljs-bean-1.9.0.jar
clout/clout/2.1.2/clout-2.1.2.jar
@@ -26,36 +26,35 @@ com/github/javaparser/javaparser-core/3.25.3/javaparser-core-3.25.3.jar
com/google/auto/value/auto-value-annotations/1.6/auto-value-annotations-1.6.jar
com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar
com/google/code/gson/gson/2.9.1/gson-2.9.1.jar
com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar
com/google/errorprone/error_prone_annotations/2.15.0/error_prone_annotations-2.15.0.jar
com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar
com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar
com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar
com/google/javascript/closure-compiler-unshaded/v20230802/closure-compiler-unshaded-v20230802.jar
com/google/javascript/closure-compiler-unshaded/v20230411/closure-compiler-unshaded-v20230411.jar
com/google/protobuf/protobuf-java/3.21.12/protobuf-java-3.21.12.jar
com/google/re2j/re2j/1.3/re2j-1.3.jar
com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar
com/taoensso/encore/3.68.0/encore-3.68.0.jar
com/taoensso/timbre/6.3.1/timbre-6.3.1.jar
com/taoensso/truss/1.11.0/truss-1.11.0.jar
com/taoensso/tufte/2.6.3/tufte-2.6.3.jar
commons-codec/commons-codec/1.15/commons-codec-1.15.jar
commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.jar
commons-io/commons-io/2.11.0/commons-io-2.11.0.jar
compojure/compojure/1.5.2/compojure-1.5.2.jar
com/taoensso/encore/3.68.0/encore-3.68.0.jar
com/taoensso/timbre/6.3.1/timbre-6.3.1.jar
com/taoensso/truss/1.11.0/truss-1.11.0.jar
com/taoensso/tufte/2.6.3/tufte-2.6.3.jar
crypto-equality/crypto-equality/1.0.1/crypto-equality-1.0.1.jar
crypto-random/crypto-random/1.2.1/crypto-random-1.2.1.jar
day8/re-frame/test/0.1.5/test-0.1.5.jar
expound/expound/0.9.0/expound-0.9.0.jar
fipp/fipp/0.6.26/fipp-0.6.26.jar
funcool/promesa/11.0.678/promesa-11.0.678.jar
hiccup/hiccup/1.0.5/hiccup-1.0.5.jar
http-kit/http-kit/2.2.0/http-kit-2.2.0.jar
instaparse/instaparse/1.4.0/instaparse-1.4.0.jar
io/aviso/pretty/1.4.4/pretty-1.4.4.jar
io/methvin/directory-watcher/0.17.1/directory-watcher-0.17.1.jar
io/replikativ/datalog-parser/0.2.25/datalog-parser-0.2.25.jar
io/undertow/undertow-core/2.3.10.Final/undertow-core-2.3.10.Final.jar
io/undertow/undertow-core/2.2.4.Final/undertow-core-2.2.4.Final.jar
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
@@ -65,12 +64,12 @@ 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
nrepl/nrepl/0.9.0/nrepl-0.9.0.jar
nrepl/nrepl/1.0.0/nrepl-1.0.0.jar
nubank/matcher-combinators/3.8.8/matcher-combinators-3.8.8.jar
org/apache/ant/ant-launcher/1.10.11/ant-launcher-1.10.11.jar
org/apache/ant/ant/1.10.11/ant-1.10.11.jar
org/babashka/sci.impl.types/0.0.2/sci.impl.types-0.0.2.jar
org/apache/ant/ant-launcher/1.10.11/ant-launcher-1.10.11.jar
org/babashka/sci/0.7.38/sci-0.7.38.jar
org/babashka/sci.impl.types/0.0.2/sci.impl.types-0.0.2.jar
org/checkerframework/checker-qual/3.12.0/checker-qual-3.12.0.jar
org/clojure/clojure/1.11.1/clojure-1.11.1.jar
org/clojure/clojurescript/1.11.60/clojurescript-1.11.60.jar
@@ -81,42 +80,41 @@ org/clojure/core.rrb-vector/0.1.2/core.rrb-vector-0.1.2.jar
org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.jar
org/clojure/data.json/2.4.0/data.json-2.4.0.jar
org/clojure/data.priority-map/1.1.0/data.priority-map-1.1.0.jar
org/clojure/google-closure-library-third-party/0.0-20230227-c7c0a541/google-closure-library-third-party-0.0-20230227-c7c0a541.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/math.combinatorics/0.2.0/math.combinatorics-0.2.0.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.jvm/1.2.2/tools.analyzer.jvm-1.2.2.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
org/clojure/tools.logging/1.1.0/tools.logging-1.1.0.jar
org/clojure/tools.macro/0.1.5/tools.macro-0.1.5.jar
org/clojure/tools.reader/1.3.7/tools.reader-1.3.7.jar
org/clojure/tools.reader/1.3.6/tools.reader-1.3.6.jar
org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar
org/jboss/logging/jboss-logging/3.4.3.Final/jboss-logging-3.4.3.Final.jar
org/jboss/threads/jboss-threads/3.5.0.Final/jboss-threads-3.5.0.Final.jar
org/jboss/xnio/xnio-api/3.8.8.Final/xnio-api-3.8.8.Final.jar
org/jboss/xnio/xnio-nio/3.8.8.Final/xnio-nio-3.8.8.Final.jar
org/jboss/logging/jboss-logging/3.4.1.Final/jboss-logging-3.4.1.Final.jar
org/jboss/threads/jboss-threads/3.1.0.Final/jboss-threads-3.1.0.Final.jar
org/jboss/xnio/xnio-api/3.8.0.Final/xnio-api-3.8.0.Final.jar
org/jboss/xnio/xnio-nio/3.8.0.Final/xnio-nio-3.8.0.Final.jar
org/jspecify/jspecify/0.2.0/jspecify-0.2.0.jar
org/msgpack/msgpack/0.6.12/msgpack-0.6.12.jar
org/ow2/asm/asm/9.4/asm-9.4.jar
org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar
org/slf4j/slf4j-nop/2.0.9/slf4j-nop-2.0.9.jar
org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar
org/wildfly/client/wildfly-client-config/1.0.1.Final/wildfly-client-config-1.0.1.Final.jar
org/wildfly/common/wildfly-common/1.5.4.Final/wildfly-common-1.5.4.Final.jar
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
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
reagent/reagent/1.2.0/reagent-1.2.0.jar
refactor-nrepl/refactor-nrepl/3.9.1/refactor-nrepl-3.9.1.jar
ring-cors/ring-cors/0.1.8/ring-cors-0.1.8.jar
ring/ring-codec/1.2.0/ring-codec-1.2.0.jar
ring/ring-core/1.9.6/ring-core-1.9.6.jar
thheller/shadow-client/1.3.3/shadow-client-1.3.3.jar
thheller/shadow-cljs/2.26.2/shadow-cljs-2.26.2-aot.jar
thheller/shadow-cljs/2.25.0/shadow-cljs-2.25.0-aot.jar
thheller/shadow-cljsjs/0.0.22/shadow-cljsjs-0.0.22.jar
thheller/shadow-undertow/0.3.3/shadow-undertow-0.3.3.jar
thheller/shadow-undertow/0.3.1/shadow-undertow-0.3.1.jar
thheller/shadow-util/0.7.0/shadow-util-0.7.0.jar
tigris/tigris/0.1.2/tigris-0.1.2.jar
-10
View File
@@ -53,14 +53,6 @@ copyNodeModules() {
# Find files that were modified and should cause a re-copying of node modules.
# Some files are generated/modified by build processes and should be ignored.
#
# react-native/ReactCommon/react/renderer/components/rncore/*
# generated at runtime by react-native
# related code https://github.com/facebook/react-native/blob/v0.72.5/packages/react-native/ReactCommon/React-rncore.podspec#L19
#
# react-native-config/ios/ReactNativeConfig/GeneratedDotEnv.m
# generated at runtime by react-native-config
# related code https://github.com/lugg/react-native-config/blob/v1.5.0/react-native-config.podspec#L52
findFilesNewerThan() {
local sentinel="${1}"
local dir="${2}"
@@ -69,8 +61,6 @@ findFilesNewerThan() {
-not -ipath "*/*android/build/*" -prune \
-not -ipath "*/xcuserdata/*" -prune \
-not -ipath "*/scripts/.packager.env" \
-not -path "*/node_modules/react-native/ReactCommon/react/renderer/components/rncore/*" \
-not -path "*/node_modules/react-native-config/ios/ReactNativeConfig/GeneratedDotEnv.m" \
-print
}
-1
View File
@@ -10,7 +10,6 @@
[com.cognitect/transit-cljs "0.8.280"]
[camel-snake-kebab "0.4.3"]
[metosin/malli "0.13.0"]
[funcool/promesa "11.0.678"]
;; Dev dependencies
[refactor-nrepl "3.9.1"]
+4
View File
@@ -3,6 +3,7 @@
["eth-phishing-detect" :as eth-phishing-detect]
[clojure.string :as string]
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
[legacy.status-im.browser.eip3085 :as eip3085]
[legacy.status-im.browser.eip3326 :as eip3326]
[legacy.status-im.browser.permissions :as browser.permissions]
[legacy.status-im.browser.webview-ref :as webview-ref]
@@ -442,6 +443,9 @@
(= method "wallet_switchEthereumChain")
(eip3326/handle-switch-ethereum-chain cofx dapp-name id message-id (first params))
(= method "wallet_addEthereumChain")
(eip3085/handle-add-ethereum-chain cofx dapp-name id message-id (first params))
:else
{:browser/call-rpc [payload
#(re-frame/dispatch [:browser.callback/call-rpc
+38
View File
@@ -2,6 +2,10 @@
;(`wallet_addEthereumChain`)
(ns legacy.status-im.browser.eip3085
(:require
[clojure.string :as string]
[legacy.status-im.network.core :as network]
[legacy.status-im.ui.screens.browser.eip3085.sheet :as sheet]
[legacy.status-im.utils.random :as random]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[taoensso.timbre :as log]
@@ -36,3 +40,37 @@
:message "User rejected the request."}}
:dispatch [:bottom-sheet/hide-old]})
(rf/defn handle-add-ethereum-chain
{:events [:eip3085/handle-add-ethereum-chain]}
[{{:networks/keys [networks] :as db} :db :as cofx}
dapp-name id message-id
{:keys [chainId blockExplorerUrls chainName iconUrls nativeCurrency rpcUrls] :as params}]
(let [manage {:name {:value chainName}
:symbol {:value (:symbol nativeCurrency)}
:url {:value (first rpcUrls)}
:network-id {:value chainId}
:chain {:value :custom}}]
(if (network/valid-manage? manage)
(let [{:keys [name url chain network-id symbol]} manage
random-id (string/replace (random/id) "-" "")
network (network/new-network random-id
(:value name)
(:value symbol)
(:value url)
(:value chain)
(:value network-id))
new-networks (assoc networks random-id network)
params (assoc params
:new-networks new-networks
:id id
:new-network network)]
(if (network/chain-id-available? networks network)
{:dispatch [:bottom-sheet/show-sheet-old
{:content (fn []
[sheet/permissions-panel dapp-name message-id params])}]}
(send-success-call-to-bridge cofx id message-id)))
{:browser/send-to-bridge {:type constants/web3-send-async-callback
:messageId message-id
:error {:code -32602
:message "invalid network parameters"}}})))
+3 -3
View File
@@ -45,12 +45,12 @@
(query-fn (comp participant-set :public-key) (vals all-contacts))))
(defn get-all-contacts-in-group-chat
[members admins contacts {:keys [public-key preferred-name name display-name] :as current-account}]
[members admins contacts {:keys [public-key preferred-name name] :as current-account}]
(let [current-contact (some->
current-account
(select-keys [:name :preferred-name :public-key :images :compressed-key])
(select-keys [:name :preferred-name :public-key :images])
(set/rename-keys {:name :alias :preferred-name :name})
(assoc :primary-name (or display-name preferred-name name)))
(assoc :primary-name (or preferred-name name)))
all-contacts (cond-> contacts
current-contact
(assoc public-key current-contact))]
@@ -29,39 +29,37 @@
(testing "transforms messages from RPC response"
(is
(= {:last-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:link-previews []
:status-link-previews []
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}
(= {:last-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:link-previews []
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}
:message nil
:reply-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:link-previews []
:status-link-previews []
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}}
:reply-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:link-previews []
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}}
(-> raw-notification
store/<-rpc
(select-keys [:last-message :message :reply-message])))))
@@ -4,6 +4,13 @@
[clojure.walk :as walk]
[status-im.constants :as constants]))
(defn rpc->channel-permissions
[rpc-channels-permissions]
(update-vals rpc-channels-permissions
(fn [{:keys [viewAndPostPermissions viewOnlyPermissions]}]
{:view-only (set/rename-keys viewOnlyPermissions {:satisfied :satisfied?})
:view-and-post (set/rename-keys viewAndPostPermissions {:satisfied :satisfied?})})))
(defn <-revealed-accounts-rpc
[accounts]
(mapv
@@ -27,9 +34,8 @@
(assoc acc
(name k)
(-> v
(assoc :token-gated? (:tokenGated v)
:can-post? (:canPost v))
(dissoc :canPost :tokenGated)
(assoc :can-post? (:canPost v))
(dissoc :canPost)
(update :members walk/stringify-keys))))
{}
chats))
@@ -0,0 +1,23 @@
(ns legacy.status-im.data-store.communities-test
(:require
[cljs.test :refer [deftest is]]
[legacy.status-im.data-store.communities :as sut]))
(def permissions
{"community-id-chat-1"
{:viewOnlyPermissions {:satisfied false
:permissions {:token-permission-id-01 {:criteria [false]}}}
:viewAndPostPermissions {:satisfied true :permissions {}}}
"community-id-chat-2"
{:viewOnlyPermissions {:satisfied true :permissions {}}
:viewAndPostPermissions {:satisfied true :permissions {}}}})
(deftest rpc->channel-permissions-test
(is (= {"community-id-chat-1"
{:view-only {:satisfied? false
:permissions {:token-permission-id-01 {:criteria [false]}}}
:view-and-post {:satisfied? true :permissions {}}}
"community-id-chat-2"
{:view-only {:satisfied? true :permissions {}}
:view-and-post {:satisfied? true :permissions {}}}}
(sut/rpc->channel-permissions permissions))))
+3 -33
View File
@@ -16,41 +16,13 @@
:community-id :communityId
:clock-value :clock})))
(defn- <-status-link-previews-rpc
[preview]
(-> preview
(update :community
set/rename-keys
{:communityId :community-id
:displayName :display-name
:membersCount :members-count
:activeMembersCount :active-members-count})
(update-in [:community :banner] set/rename-keys {:dataUri :data-uri})
(update-in [:community :icon] set/rename-keys {:dataUri :data-uri})))
(defn ->status-link-previews-rpc
[preview]
(-> preview
(update :community
set/rename-keys
{:community-id :communityId
:display-name :displayName
:members-count :membersCount
:active-members-count :activeMembersCount})
(update-in [:community :banner] set/rename-keys {:data-uri :dataUri})
(update-in [:community :icon] set/rename-keys {:data-uri :dataUri})))
(defn- <-link-preview-rpc
[preview]
(-> preview
(update :thumbnail set/rename-keys {:dataUri :data-uri})
(update :favicon set/rename-keys {:dataUri :data-uri})))
(update preview :thumbnail set/rename-keys {:dataUri :data-uri}))
(defn ->link-preview-rpc
[preview]
(-> preview
(update :thumbnail set/rename-keys {:data-uri :dataUri})
(update :favicon set/rename-keys {:data-uri :dataUri})))
(update preview :thumbnail set/rename-keys {:data-uri :dataUri}))
(defn <-rpc
[message]
@@ -81,10 +53,8 @@
:new :new?
:albumImagesCount :album-images-count
:displayName :display-name
:linkPreviews :link-previews
:statusLinkPreviews :status-link-previews})
:linkPreviews :link-previews})
(update :link-previews #(map <-link-preview-rpc %))
(update :status-link-previews #(map <-status-link-previews-rpc %))
(update :quoted-message
set/rename-keys
{:parsedText :parsed-text
@@ -37,19 +37,7 @@
:compressed-key "c"
:timestamp 3
:link-previews [{:thumbnail {:url "http://localhost"
:data-uri "data:image/png"}
:favicon nil}]
:status-link-previews [{:url "http://localhost"
:community {:community-id "0x01"
:display-name "Test Comm"
:members-count 12
:active-members-count 12
:banner
{:data-uri
"data:image/png"}
:icon
{:data-uri
"data:image/png"}}}]}
:data-uri "data:image/png"}}]}
message {:id message-id
:whisperTimestamp 1
:parsedText "parsed-text"
@@ -73,17 +61,5 @@
:timestamp 3
:outgoingStatus "sending"
:linkPreviews [{:thumbnail {:url "http://localhost"
:dataUri "data:image/png"}
:favicon nil}]
:statusLinkPreviews [{:url "http://localhost"
:community {:communityId "0x01"
:displayName "Test Comm"
:membersCount 12
:activeMembersCount 12
:banner
{:dataUri
"data:image/png"}
:icon
{:dataUri
"data:image/png"}}}]}]
:dataUri "data:image/png"}}]}]
(is (= expected (m/<-rpc message))))))
+3 -2
View File
@@ -37,6 +37,7 @@
[react-native.core :as rn]
[react-native.permissions :as permissions]
[react-native.platform :as platform]
[status-im.common.biometric.events :as biometric]
status-im.common.serialization
status-im.common.standard-authentication.events
[status-im.common.theme.core :as theme]
@@ -119,7 +120,7 @@
:content (i18n/label :t/biometric-auth-confirm-message)
:confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again)
:cancel-button-text (i18n/label :t/biometric-auth-confirm-logout)
:on-accept #(rf/dispatch [:biometric/authenticate {:on-fail on-biometric-auth-fail}])
:on-accept #(biometric/authenticate nil {:on-fail on-biometric-auth-fail})
:on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])})))
(rf/defn on-return-from-background
@@ -141,7 +142,7 @@
#(when-let [chat-id (:current-chat-id db)]
{:dispatch [:chat/mark-all-as-read chat-id]})
#(when requires-bio-auth
{:dispatch [:biometric/authenticate {:on-fail on-biometric-auth-fail}]}))))
(biometric/authenticate % {:on-fail on-biometric-auth-fail})))))
(rf/defn on-going-in-background
[{:keys [db now]}]
@@ -13,14 +13,15 @@
(native-module/logout)))
(rf/defn initialize-app-db
[{{:keys [keycard initials-avatar-font-file biometrics]
:network/keys [type]}
[{{:keys [keycard initials-avatar-font-file]
:biometric/keys [supported-type]
:network/keys [type]}
:db}]
{:db (assoc db/app-db
:network/type type
:initials-avatar-font-file initials-avatar-font-file
:keycard (dissoc keycard :secrets :pin :application-info)
:biometrics biometrics
:biometric/supported-type supported-type
:syncing nil)})
(rf/defn logout-method
@@ -28,7 +29,8 @@
[{:keys [db] :as cofx} {:keys [auth-method logout?]}]
(let [key-uid (get-in db [:profile/profile :key-uid])]
(rf/merge cofx
{:effects.shell/reset-state nil
{:set-root :progress
:effects.shell/reset-state nil
:hide-popover nil
::logout nil
:profile.settings/webview-debug-changed false
@@ -44,7 +46,6 @@
[_]
;; we need to disable notifications before starting the logout process
{:effects/push-notifications-disable nil
:dispatch [:alert-banners/remove-all]
:dispatch-later [{:ms 100
:dispatch [::logout-method
{:auth-method keychain/auth-method-none
+132 -1
View File
@@ -1,6 +1,11 @@
(ns legacy.status-im.network.core
(:require
[clojure.string :as string]))
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.navigation.events :as navigation]
[utils.ethereum.chain :as chain]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def url-regex
#"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}(\.[a-z]{2,6})?\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)")
@@ -9,6 +14,12 @@
[url]
(boolean (re-matches url-regex (str url))))
(def default-manage
{:name {:value ""}
:url {:value ""}
:symbol {:value ""}
:chain {:value :mainnet}})
(defn validate-string
[{:keys [value]}]
{:value value
@@ -45,3 +56,123 @@
[current-networks network]
(let [chain-id (get-in network [:config :NetworkId])]
(every? #(not= chain-id (get-in % [1 :config :NetworkId])) current-networks)))
(defn get-network
[{:keys [db]} network-id]
(get-in db [:networks/networks network-id]))
(rf/defn set-input
{:events [::input-changed]}
[{:keys [db]} input-key value]
{:db (-> db
(update-in [:networks/manage input-key] assoc :value value)
(update-in [:networks/manage] validate-manage))})
;; No edit functionality actually implemented
(rf/defn edit
{:events [::add-network-pressed]}
[{db :db}]
{:db (assoc db :networks/manage (validate-manage default-manage))
:dispatch [:navigate-to :edit-network]})
(rf/defn connect-success
{:events [::connect-success]}
[_ network-id]
{:ui/show-confirmation
{:title (i18n/label :t/close-app-title)
:content (i18n/label :t/logout-app-content)
:confirm-button-text (i18n/label :t/close-app-button)
:on-accept #(re-frame/dispatch [::save-network-settings-pressed network-id])
:on-cancel nil}})
(rf/defn connect-failure
{:events [::connect-failure]}
[_ reason]
{:effects.utils/show-popup
{:title (i18n/label :t/error)
:content (str reason)}})
(rf/defn connect
{:events [::connect-network-pressed]}
[{:keys [db] :as cofx} network-id]
(if (get-in db [:networks/networks network-id :config])
(connect-success cofx network-id)
(connect-failure cofx "A network with the specified id doesn't exist")))
(rf/defn delete
{:events [::delete-network-pressed]}
[{:keys [db]} network]
(let [current-network? (= (:networks/current-network db) network)]
(if (or current-network?
(not (get-in db [:networks/networks network])))
{:ui/show-error (i18n/label :t/delete-network-error)}
{:ui/show-confirmation {:title (i18n/label :t/delete-network-title)
:content (i18n/label :t/delete-network-confirmation)
:confirm-button-text (i18n/label :t/delete)
:on-accept #(re-frame/dispatch [::remove-network-confirmed
network])
:on-cancel nil}})))
(rf/defn save-network-settings
{:events [::save-network-settings-pressed]}
[{:keys [db] :as cofx} network]
{:db (assoc db :networks/current-network network)
:json-rpc/call [{:method "settings_saveSetting"
:params [:networks/current-network network]
:on-success #(re-frame/dispatch [:logout])}]})
(rf/defn remove-network
{:events [::remove-network-confirmed]}
[{:keys [db] :as cofx} network]
(let [networks (dissoc (:networks/networks db) network)]
{:db (assoc db :networks/networks networks)
:json-rpc/call [{:method "settings_saveSetting"
:params [:networks/networks (vals networks)]
:on-success #(re-frame/dispatch [:navigate-back])}]}))
(defn new-network
[random-id network-name sym upstream-url chain-type chain-id]
(let [data-dir (str "/ethereum/" (name chain-type) "_rpc")
config {:NetworkId (or (when chain-id (int chain-id))
(chain/chain-keyword->chain-id chain-type))
:DataDir data-dir
:UpstreamConfig {:Enabled true
:URL upstream-url}}]
{:id random-id
:name network-name
:symbol sym
:config config}))
(rf/defn save
{:events [::save-network-pressed]
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
[{{:networks/keys [manage networks] :as db} :db
random-id-generator :random-id-generator
:as cofx}]
(if (valid-manage? manage)
;; rename network-id from UI to chain-id
(let [{:keys [name url chain network-id symbol]} manage
random-id (string/replace (random-id-generator) "-" "")
network (new-network random-id
(:value name)
(:value symbol)
(:value url)
(:value chain)
(:value network-id))
custom-chain-type? (= :custom (:value chain))
new-networks (assoc networks random-id network)]
(if (or (not custom-chain-type?)
(chain-id-available? networks network))
{:db (-> db
(dissoc :networks/manage)
(assoc :networks/networks new-networks))
:json-rpc/call [{:method "settings_saveSetting"
:params [:networks/networks (vals new-networks)]
:on-success #(re-frame/dispatch [:navigate-back])}]}
{:ui/show-error "chain-id already defined"}))
{:ui/show-error "invalid network parameters"}))
(rf/defn open-network-details
{:events [::network-entry-pressed]}
[cofx network]
(navigation/navigate-to cofx :network-details {:networks/selected-network network}))
+130
View File
@@ -31,6 +31,38 @@
(testing "an https url not on the default port"
(is (network.core/valid-rpc-url? "https://valid.something.else:65323"))))
(deftest new-network-test
(let [actual (network.core/new-network "randomid"
"network-name"
"network-symbol"
"upstream-url"
:mainnet
nil)]
(is (= {:id "randomid"
:name "network-name"
:symbol "network-symbol"
:config {:NetworkId 1
:DataDir "/ethereum/mainnet_rpc"
:UpstreamConfig {:Enabled true
:URL "upstream-url"}}}
actual))))
(deftest new-network-id-test
(let [actual (network.core/new-network "randomid"
"network-name"
"network-symbol"
"upstream-url"
:mainnet
"5777")]
(is (= {:id "randomid"
:name "network-name"
:symbol "network-symbol"
:config {:NetworkId 5777
:DataDir "/ethereum/mainnet_rpc"
:UpstreamConfig {:Enabled true
:URL "upstream-url"}}}
actual))))
(deftest valid-manage-test
(testing "a valid manage"
(is (network.core/valid-manage? {:url {:value "http://valid.com"}
@@ -60,3 +92,101 @@
:name {:value "valid"}
:symbol {:value ""}
:chain {:value "valid"}})))))
(deftest set-input-test
(testing "it updates and validate a field"
(is
(= {:db {:networks/manage {:url {:value "http://valid.com"
:error false}
:name {:value ""
:error true}
:symbol {:value "symbol"
:error false}
:chain {:value "mainnet"
:error false}
:network-id {:value nil
:error false}}}}
(network.core/set-input {:db {:networks/manage {:url {:value "something"
:error true}
:name {:value ""
:error false}
:symbol {:value "symbol"
:error false}
:chain {:value "mainnet"
:error false}}}}
:url
"http://valid.com")))))
(deftest not-save-invalid-url
(testing "it does not save a network with an invalid url"
(is
(:ui/show-error (network.core/save {:random-id-generator (constantly "random")
:db {:networks/manage {:url {:value "wrong"}
:chain {:value "1"}
:name {:value "empty"}}
:profile/profile {}}})))))
(deftest save-valid-network
(testing "save a valid network"
(let [fx (network.core/save
{:random-id-generator (constantly "random-id")
:db {:networks/manage {:url {:value "http://valid.com"}
:chain {:value :mainnet}
:symbol {:value "symbol"}
:name {:value "valid"}}
:profile/profile {}
:networks/networks {"random2"
{:id "random2"
:name "network-name"
:symbol "symbol"
:config {:NetworkId 1
:DataDir "/ethereum/mainnet_rpc"
:UpstreamConfig
{:Enabled true
:URL "upstream-url"}}}}}})]
(is (= "settings_saveSetting" (:method (first (:json-rpc/call fx)))))
(is (nil? (:networks/manage (:db fx))))
(testing "and check that it has an id with `-` and the correct mainnet NetworkId"
(is (= 1 (get-in fx [:db :networks/networks "randomid" :config :NetworkId])))))))
(deftest not-save-custom-chain-with-non-unique-id
(testing "it does not save a custom chain with network-id already defined"
(let [result (network.core/save
{:random-id-generator (constantly "already-defined")
:db {:networks/manage {:url {:value "http://valid.com"}
:chain {:value :custom}
:name {:value "valid"}
:network-id {:value 1}}
:profile/profile {}
:networks/networks {"random"
{:id "random"
:name "network-name"
:config {:NetworkId 1
:DataDir "/ethereum/mainnet_rpc"
:UpstreamConfig
{:Enabled true
:URL "upstream-url"}}}}}})]
(is (:ui/show-error result)))))
(deftest save-valid-network-with-unique-chain-id-check
(testing "save a valid network with chain-id not already defined"
(let [fx (network.core/save
{:random-id-generator (constantly "random")
:db {:networks/manage {:url {:value "http://valid.com"}
:chain {:value :mainnet}
:name {:value "valid"}
:symbol {:value "symbol"}
:network-id {:value 5}}
:profile/profile {}
:networks/networks {"randomid"
{:id "randomid"
:name "network-name"
:symbol "symbol"
:config {:NetworkId 3
:DataDir "/ethereum/mainnet_rpc"
:UpstreamConfig
{:Enabled true
:URL "upstream-url"}}}}}})]
(is (= "settings_saveSetting" (:method (first (:json-rpc/call fx)))))
(is (nil? (:networks/manage (:db fx))))
(is (get-in fx [:db :networks/networks "random"])))))
+39
View File
@@ -0,0 +1,39 @@
(ns legacy.status-im.subs.networks
(:require
[re-frame.core :as re-frame]
[status-im.config :as config]
[utils.ethereum.chain :as chain]))
(defn- filter-networks
[network-type]
(fn [network]
(let [chain-id (chain/network->chain-id network)
testnet? (chain/testnet? chain-id)
custom? (:custom? network)]
(case network-type
:custom custom?
:mainnet (and (not custom?) (not testnet?))
:testnet (and (not custom?) testnet?)))))
(defn- label-networks
[default-networks]
(fn [network]
(let [custom? (not (default-networks (:id network)))]
(assoc network :custom? custom?))))
(re-frame/reg-sub
:get-networks
:<- [:networks/networks]
(fn [networks]
(let [networks (map (label-networks (into #{} (map :id config/default-networks)))
(sort-by :name (vals networks)))
types [:mainnet :testnet :custom]]
(zipmap
types
(map #(filter (filter-networks %) networks) types)))))
(re-frame/reg-sub
:manage-network-valid?
:<- [:networks/manage]
(fn [manage]
(not-any? :error (vals manage))))
+1 -2
View File
@@ -5,6 +5,7 @@
legacy.status-im.subs.ens
legacy.status-im.subs.keycard
legacy.status-im.subs.mailservers
legacy.status-im.subs.networks
legacy.status-im.subs.stickers
[re-frame.core :as re-frame]))
@@ -22,7 +23,6 @@
(reg-root-key-sub :peers-count :peers-count)
(reg-root-key-sub :peers-summary :peers-summary)
(reg-root-key-sub :web3-node-version :web3-node-version)
(reg-root-key-sub :alert-banners :alert-banners)
;;keycard
(reg-root-key-sub :keycard :keycard)
@@ -100,7 +100,6 @@
;;; Link previews
(reg-root-key-sub :link-previews-whitelist :link-previews-whitelist)
(reg-root-key-sub :chat/link-previews :chat/link-previews)
(reg-root-key-sub :chat/status-link-previews :chat/status-link-previews)
;;commands
(reg-root-key-sub :commands/select-account :commands/select-account)
@@ -13,21 +13,37 @@
(re-frame/dispatch event))
(defn- normal-mode-settings-data
[{:keys [current-log-level
[{:keys [network-name
current-log-level
light-client-enabled?
transactions-management-enabled?
current-fleet
webview-debug
test-networks-enabled?
is-goerli-enabled?
peer-syncing-enabled?]}]
is-goerli-enabled?]}]
(keep
identity
[{:size :small
:title (i18n/label :t/network)
:accessibility-label :network-button
:container-margin-top 8
:on-press
#(re-frame/dispatch [:navigate-to :network-settings])
:accessory :text
:accessory-text network-name
:chevron true}
{:size :small
:title (i18n/label :t/network-info)
:accessibility-label :network-button
:container-margin-top 8
:on-press
#(re-frame/dispatch [:navigate-to :network-info])
:chevron true}
{:size :small
:title (i18n/label :t/log-level)
:accessibility-label :log-level-settings-button
:on-press
#(re-frame/dispatch [:open-modal :log-level-settings])
#(re-frame/dispatch [:navigate-to :log-level-settings])
:accessory :text
:accessory-text current-log-level
:chevron true}
@@ -35,7 +51,7 @@
:title (i18n/label :t/fleet)
:accessibility-label :fleet-settings-button
:on-press
#(re-frame/dispatch [:open-modal :fleet-settings])
#(re-frame/dispatch [:navigate-to :fleet-settings])
:accessory :text
:accessory-text current-fleet
:chevron true}
@@ -50,14 +66,14 @@
:accessibility-label :rpc-usage-info
:container-margin-top 8
:on-press
#(re-frame/dispatch [:open-modal :rpc-usage-info])
#(re-frame/dispatch [:navigate-to :rpc-usage-info])
:chevron true}
{:size :small
:title (i18n/label :t/peers-stats)
:accessibility-label :peers-stats
:container-margin-top 8
:on-press
#(re-frame/dispatch [:open-modal :peers-stats])
#(re-frame/dispatch [:navigate-to :peers-stats])
:chevron true}
{:size :small
:title (i18n/label :t/light-client-enabled)
@@ -103,19 +119,11 @@
#(re-frame/dispatch [:profile.settings/toggle-goerli-test-network])
:accessory :switch
:active is-goerli-enabled?}
{:size :small
:title "Peer syncing"
:accessibility-label :peer-syncing
:container-margin-bottom 8
:on-press
#(re-frame/dispatch [:profile.settings/toggle-peer-syncing])
:accessory :switch
:active peer-syncing-enabled?}
{:size :small
:title (i18n/label :t/set-currency)
:accessibility-label :wallet-change-currency
:on-press #(hide-sheet-and-dispatch
[:open-modal :currency-settings])
[:navigate-to :currency-settings])
:chevron true}]))
(defn- flat-list-data
@@ -134,20 +142,20 @@
is-goerli-enabled? [:profile/is-goerli-enabled?]
light-client-enabled? [:profile/light-client-enabled?]
webview-debug [:profile/webview-debug]
network-name [:network-name]
transactions-management-enabled? [:wallet-legacy/transactions-management-enabled?]
current-log-level [:log-level/current-log-level]
current-fleet [:fleets/current-fleet]
peer-syncing-enabled? [:profile/peer-syncing-enabled?]]
current-fleet [:fleets/current-fleet]]
[list/flat-list
{:data (flat-list-data
{:current-log-level current-log-level
{:network-name network-name
:current-log-level current-log-level
:transactions-management-enabled? transactions-management-enabled?
:light-client-enabled? light-client-enabled?
:current-fleet current-fleet
:dev-mode? false
:webview-debug webview-debug
:test-networks-enabled? test-networks-enabled?
:is-goerli-enabled? is-goerli-enabled?
:peer-syncing-enabled? peer-syncing-enabled?})
:is-goerli-enabled? is-goerli-enabled?})
:key-fn (fn [_ i] (str i))
:render-fn render-item}]))
@@ -85,7 +85,9 @@
:accessibility-label :share-community-link
:type :secondary
:on-press #(debounce/throttle-and-dispatch
[::communities/share-community-confirmation-pressed @user-pk
[(if can-invite?
::communities/invite-people-confirmation-pressed
::communities/share-community-confirmation-pressed) @user-pk
selected]
3000)}
(i18n/label (if can-invite? :t/invite :t/share))]}]]))))
@@ -0,0 +1,12 @@
(ns legacy.status-im.ui.screens.network.edit-network.styles)
(def edit-network-view
{:flex 1
:margin-horizontal 16
:margin-vertical 15})
(def bottom-container
{:flex-direction :row
:padding-vertical 4})
(def container {:flex 1})
@@ -0,0 +1,72 @@
(ns legacy.status-im.ui.screens.network.edit-network.views
(:require
[legacy.status-im.network.core :as network]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.list.views :as list]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.screens.network.edit-network.styles :as styles]
[re-frame.core :as re-frame]
[utils.i18n :as i18n])
(:require-macros [legacy.status-im.utils.views :as views]))
(defn- render-network-type
[type _ _ manage-network]
(let [name (case type
:mainnet (i18n/label :t/mainnet-network)
:goerli (i18n/label :t/goerli-network)
:custom (i18n/label :t/custom))]
[list.item/list-item
{:title name
:accessory :radio
:active (= (get-in manage-network [:chain :value]) type)
:on-press #(re-frame/dispatch [::network/input-changed :chain type])}]))
(views/defview edit-network
[]
(views/letsubs [manage-network [:networks/manage]
is-valid? [:manage-network-valid?]]
(let [custom? (= (get-in manage-network [:chain :value]) :custom)]
[react/keyboard-avoiding-view {:flex 1}
[react/scroll-view
[react/view styles/edit-network-view
[react/view {:padding-vertical 8}
[quo/text-input
{:label (i18n/label :t/name)
:placeholder (i18n/label :t/specify-name)
:default-value (get-in manage-network [:name :value])
:on-change-text #(re-frame/dispatch [::network/input-changed :name %])
:auto-focus true}]]
[react/view {:padding-vertical 8}
[quo/text-input
{:label (i18n/label :t/symbol)
:placeholder (i18n/label :t/specify-symbol)
:default-value (get-in manage-network [:symbol :value])
:on-change-text #(re-frame/dispatch [::network/input-changed :symbol %])
:auto-focus true}]]
[react/view {:padding-vertical 8}
[quo/text-input
{:label (i18n/label :t/rpc-url)
:placeholder (i18n/label :t/specify-rpc-url)
:default-value (get-in manage-network [:url :value])
:on-change-text #(re-frame/dispatch [::network/input-changed :url %])}]]]
[quo/list-header (i18n/label :t/network-chain)]
[list/flat-list
{:data [:mainnet :goerli :custom]
:key-fn (fn [_ i] (str i))
:render-data manage-network
:render-fn render-network-type}]
(when custom?
[react/view styles/edit-network-view
[quo/text-input
{:label (i18n/label :t/network-id)
:placeholder (i18n/label :t/specify-network-id)
:on-change-text #(re-frame/dispatch [::network/input-changed :network-id %])}]])]
[react/view styles/bottom-container
[react/view {:flex 1}]
[quo/button
{:after :main-icons/next
:type :secondary
:disabled (not is-valid?)
:on-press #(re-frame/dispatch [::network/save-network-pressed])}
(i18n/label :t/save)]]])))
@@ -0,0 +1,47 @@
(ns legacy.status-im.ui.screens.network.network-details.views
(:require
[legacy.status-im.network.core :as network]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.screens.network.styles :as st]
[legacy.status-im.ui.screens.network.views :as network-settings]
[re-frame.core :as re-frame]
[utils.debounce :refer [throttle-and-dispatch]]
[utils.i18n :as i18n])
(:require-macros [legacy.status-im.utils.views :as views]))
(views/defview network-details
[]
(views/letsubs [{:keys [networks/selected-network]} [:get-screen-params]
current-network [:networks/current-network]
networks [:get-networks]]
(let [{:keys [id name config]} selected-network
connected? (= id current-network)
custom? (seq (filter #(= (:id %) id) (:custom networks)))]
[:<>
[react/view {:flex 1}
[network-settings/network-badge
{:name name
:connected? connected?}]
(when-not connected?
[react/touchable-highlight
{:on-press #(throttle-and-dispatch [::network/connect-network-pressed id] 1000)}
[react/view st/connect-button-container
[react/view
{:style st/connect-button
:accessibility-label :network-connect-button}
[react/text {:style st/connect-button-label}
(i18n/label :t/connect)]]
[react/i18n-text
{:style st/connect-button-description
:key :connecting-requires-login}]]])
[react/view (st/network-config-container)
[react/text
{:style st/network-config-text
:accessibility-label :network-details-text}
config]]]
(when custom?
[react/view st/bottom-container
[react/view {:flex 1}
[quo/button {:on-press #(re-frame/dispatch [::network/delete-network-pressed id])}
(i18n/label :t/delete)]]])])))
@@ -0,0 +1,90 @@
(ns legacy.status-im.ui.screens.network.styles
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.utils.styles :as styles]))
(def wrapper
{:flex 1})
(def badge-name-text
{:font-size 17})
(styles/def badge-connected-text
{:color colors/gray
:ios {:margin-top 5}})
(def connect-button-container
{:margin-top 8
:margin-bottom 16
:margin-horizontal 16})
(def connect-button
{:height 52
:align-items :center
:justify-content :center
:background-color colors/blue
:border-radius 8
:ios {:opacity 0.9}})
(def connect-button-label
{:color colors/white-persist
:font-size 17})
(def connect-button-description
{:color colors/gray
:margin-top 8
:height 20})
(styles/defn network-config-container
[]
{:height 160
:margin-top 8
:padding-top 16
:padding-left 16
:margin-horizontal 16
:background-color colors/gray-lighter
:ios {:border-radius 9
:opacity 0.9}
:android {:border-radius 4}})
(styles/def network-config-text
{:font-size 17
:ios {:opacity 0.8}
:android {:opacity 0.4}})
(defn network-icon
[connected? size]
{:width size
:height size
:border-radius (/ size 2)
:background-color (if connected? colors/blue colors/gray-lighter)
:align-items :center
:justify-content :center})
(def network-badge
{:height 88
:padding-left 16
:flex-direction :row
:align-items :center})
(styles/def network-item
{:flex-direction :row
:align-items :center
:padding-horizontal 16
:ios {:height 64}
:android {:height 56}})
(def network-item-name-text
{:font-size 17})
(def network-item-connected-text
{:color colors/gray
:font-size 14
:margin-top 6})
(def bottom-container
{:flex-direction :row
:margin-horizontal 12
:margin-vertical 15})
(def container {:flex 1})
@@ -0,0 +1,71 @@
(ns legacy.status-im.ui.screens.network.views
(:require
[legacy.status-im.network.core :as network]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.list.views :as list]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.components.topbar :as topbar]
[legacy.status-im.ui.screens.network.styles :as styles]
[re-frame.core :as re-frame]
[utils.i18n :as i18n])
(:require-macros [legacy.status-im.utils.views :as views]))
(defn- network-icon
[connected? size]
[react/view (styles/network-icon connected? size)
[icons/icon :main-icons/network {:color (if connected? colors/white-persist colors/gray)}]])
(defn network-badge
[& [{:keys [name connected?]}]]
[react/view styles/network-badge
[network-icon connected? 56]
[react/view {:padding-left 16}
[react/text {:style styles/badge-name-text}
(or name (i18n/label :t/new-network))]
(when connected?
[react/i18n-text
{:style styles/badge-connected-text
:key :connected}])]])
(def mainnet?
#{"mainnet" "mainnet_rpc"})
(defn render-network
[{:keys [id name] :as network} _ _ current-network]
(let [connected? (= id current-network)]
[list.item/list-item
{:on-press #(re-frame/dispatch [::network/network-entry-pressed network])
:icon :main-icons/network
:icon-bg-color (if connected? colors/blue colors/gray-lighter)
:icon-color (if connected? colors/white-persist colors/gray)
:title name
:subtitle (when connected? (i18n/label :t/connected))
:chevron true}]))
(views/defview network-settings
[]
(views/letsubs [current-network [:networks/current-network]
networks [:get-networks]]
[:<>
[topbar/topbar
{:title (i18n/label :t/network-settings)
:right-accessories
[{:icon :main-icons/add
:on-press #(re-frame/dispatch [::network/add-network-pressed])}]}]
[react/view styles/wrapper
[list/section-list
{:sections [{:title (i18n/label :t/main-networks)
:key :mainnet
:data (:mainnet networks)}
{:title (i18n/label :t/test-networks)
:key :testnet
:data (:testnet networks)}
{:title (i18n/label :t/custom-networks)
:key :custom
:data (:custom networks)}]
:key-fn :id
:default-separator? true
:render-data current-network
:render-fn render-network}]]]))
@@ -0,0 +1,84 @@
(ns legacy.status-im.ui.screens.network-info.views
(:require
[legacy.status-im.ethereum.decode :as decode]
[legacy.status-im.ui.components.react :as react]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.common.json-rpc.events :as json-rpc]
[utils.datetime :as datetime]))
(defn get-block
[block callback]
(json-rpc/call
{:method "eth_getBlockByNumber"
:params [block false]
:on-success callback
:on-error #(js/alert (str "can't fetch latest block" %))}))
(defn to-date
[timestamp]
(datetime/timestamp->long-date
(* 1000 timestamp)))
(defn check-lag
[]
(let [latest-block (reagent/atom nil)
last-loaded-block (reagent/atom nil)
on-press
(fn []
(get-block
"latest"
(fn [res]
(reset! latest-block res)
(get-block
(str "0x"
(native-module/number-to-hex
@(re-frame/subscribe [:ethereum/current-block])))
(fn [res]
(reset! last-loaded-block res))))))]
(fn []
[react/view
{:style {:flex 1
:margin-horizontal 16}}
(if-not @latest-block
[react/text
{:on-press on-press}
"PRESS TO REFRESH"]
[react/text
{:on-press on-press}
(let [latest-block-number
(decode/uint (:number @latest-block))
latest-block-timestamp
(decode/uint (:timestamp @latest-block))
last-loaded-block-number
(decode/uint (:number @last-loaded-block))
last-loaded-block-timestamp
(decode/uint (:timestamp @last-loaded-block))]
(str "Latest block number: "
latest-block-number
"\n"
"Latest block time: "
(to-date latest-block-timestamp)
"\n"
"Last loaded block: "
last-loaded-block-number
"\n"
"Last loaded block time: "
(to-date last-loaded-block-timestamp)
"\n"
"Seconds diff: "
(- latest-block-timestamp
last-loaded-block-timestamp)
"\n"
"Blocks diff: " (- latest-block-number
last-loaded-block-number)
"\n"
"PRESS TO REFRESH"))])])))
(defn network-info
[]
[check-lag])
@@ -102,7 +102,7 @@
:chevron true
:icon :main-icons/username}
registrar
(assoc :on-press #(re-frame/dispatch [:open-modal :ens-main registrar])))]
(assoc :on-press #(re-frame/dispatch [:navigate-to :ens-main registrar])))]
[list.item/list-item
{:title (i18n/label :t/contacts)
:icon :main-icons/in-contacts
@@ -112,7 +112,7 @@
(str active-contacts-count)
(i18n/label :t/none))
:chevron true
:on-press #(re-frame/dispatch [:open-modal :contacts-list])}]
:on-press #(re-frame/dispatch [:navigate-to :contacts-list])}]
[react/view {:padding-top 16}
[quo/list-header (i18n/label :t/settings)]]
[list.item/list-item
@@ -122,71 +122,71 @@
:chevron true
:accessory (when mnemonic
[components.common/counter {:size 22} 1])
:on-press #(re-frame/dispatch [:open-modal :privacy-and-security])}]
:on-press #(re-frame/dispatch [:navigate-to :privacy-and-security])}]
(when config/quo-preview-enabled?
[list.item/list-item
{:icon :main-icons/appearance
:title "Quo Preview"
:accessibility-label :appearance-settings-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :quo-preview])}])
: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 [:open-modal :status-im-preview])}])
:on-press #(re-frame/dispatch [:navigate-to :status-im-preview])}])
[list.item/list-item
{:icon :main-icons/appearance
:title (i18n/label :t/appearance)
:accessibility-label :appearance-settings-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :appearance])}]
:on-press #(re-frame/dispatch [:navigate-to :appearance])}]
[list.item/list-item
{:icon :main-icons/notification
:title (i18n/label :t/notifications)
:accessibility-label :notifications-settings-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :notifications])}]
:on-press #(re-frame/dispatch [:navigate-to :notifications])}]
[list.item/list-item
{:icon :main-icons/mobile
:title (i18n/label :t/sync-settings)
:accessibility-label :sync-settings-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :sync-settings])}]
:on-press #(re-frame/dispatch [:navigate-to :sync-settings])}]
(when keycard-pairing
[list.item/list-item
{:icon :main-icons/keycard
:title (i18n/label :t/keycard)
:accessibility-label :keycard-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :keycard-settings])}])
:on-press #(re-frame/dispatch [:navigate-to :keycard-settings])}])
[list.item/list-item
{:icon :main-icons/settings-advanced
:title (i18n/label :t/advanced)
:accessibility-label :advanced-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :advanced-settings])}]
:on-press #(re-frame/dispatch [:navigate-to :advanced-settings])}]
[list.item/list-item
{:icon :main-icons/help
:title (i18n/label :t/need-help)
:accessibility-label :help-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :help-center])}]
:on-press #(re-frame/dispatch [:navigate-to :help-center])}]
[list.item/list-item
{:icon :main-icons/info
:title (i18n/label :t/about-app)
:accessibility-label :about-button
:chevron true
:on-press #(re-frame/dispatch [:open-modal :about-app])}]
:on-press #(re-frame/dispatch [:navigate-to :about-app])}]
(when local-pairing-mode-enabled?
[list.item/list-item
{:icon :i/mobile
:title (i18n/label :t/syncing)
:accessibility-label :syncing
:chevron true
:on-press #(re-frame/dispatch [:open-modal :settings-syncing])}])
:on-press #(re-frame/dispatch [:navigate-to :settings-syncing])}])
[react/view {:padding-vertical 24}
[list.item/list-item
{:icon :main-icons/log-out
+60 -47
View File
@@ -1,6 +1,7 @@
(ns legacy.status-im.ui.screens.screens
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.screens.about-app.views :as about-app]
[legacy.status-im.ui.screens.advanced-settings.views :as advanced-settings]
[legacy.status-im.ui.screens.appearance.views :as appearance]
@@ -23,6 +24,10 @@
[legacy.status-im.ui.screens.link-previews-settings.views :as link-previews-settings]
[legacy.status-im.ui.screens.log-level-settings.views :as log-level-settings]
[legacy.status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings]
[legacy.status-im.ui.screens.network-info.views :as network-info]
[legacy.status-im.ui.screens.network.edit-network.views :as edit-network]
[legacy.status-im.ui.screens.network.network-details.views :as network-details]
[legacy.status-im.ui.screens.network.views :as network]
[legacy.status-im.ui.screens.notifications-settings.views :as notifications-settings]
[legacy.status-im.ui.screens.offline-messaging-settings.edit-mailserver.views :as edit-mailserver]
[legacy.status-im.ui.screens.offline-messaging-settings.views :as offline-messaging-settings]
@@ -47,18 +52,10 @@
[status-im.contexts.chat.group-details.view :as group-details]
[utils.i18n :as i18n]))
(defn topbar-options
[title]
{:elevation 0
:title {:color (if (colors/dark?) colors/white colors/black)
:text (i18n/label title)}
:rightButtonColor (if (colors/dark?) colors/white colors/black)
:background {:color (if (colors/dark?) colors/black colors/white)}
:backButton {:color (if (colors/dark?) colors/white colors/black)
:id :legacy-back-button
:testID :back-button
:visible true
:popStackOnPress false}})
(defn right-button-options
[id icon]
{:id id
:icon (icons/icon-source icon)})
(defn screens
[]
@@ -84,7 +81,7 @@
{:name :stickers
:options {:insets {:top? true}
:topBar (topbar-options :t/sticker-market)}
:topBar {:title {:text (i18n/label :t/sticker-market)}}}
:component stickers/packs}
{:name :stickers-pack
@@ -97,7 +94,7 @@
:component group-chat/new-group}
{:name :currency-settings
:options {:topBar (topbar-options :t/main-currency)
:options {:topBar {:title {:text (i18n/label :t/main-currency)}}
:insets {:top? true}}
:component currency-settings/currency-settings}
@@ -107,27 +104,27 @@
:options {:topBar {:visible false}}
:component profile.user/my-profile}
{:name :contacts-list
:options {:topBar (topbar-options :t/contacts)
:options {:topBar {:title {:text (i18n/label :t/contacts)}}
:insets {:top? true}}
:component contacts-list/contacts-list}
{:name :ens-main
:options {:topBar (topbar-options :t/ens-usernames)
:options {:topBar {:title {:text (i18n/label :t/ens-usernames)}}
:insets {:top? true}}
:component ens/main}
{:name :ens-search
:options {:topBar (topbar-options :t/ens-your-username)
:options {:topBar {:title {:text (i18n/label :t/ens-your-username)}}
:insets {:top? true}}
:component ens/search}
{:name :ens-checkout
:options {:topBar (topbar-options :t/ens-your-username)
:options {:topBar {:title {:text (i18n/label :t/ens-your-username)}}
:insets {:top? true}}
:component ens/checkout}
{:name :ens-confirmation
:options {:topBar (topbar-options :t/ens-your-username)
:options {:topBar {:title {:text (i18n/label :t/ens-your-username)}}
:insets {:top? true}}
:component ens/confirmation}
{:name :ens-terms
:options {:topBar (topbar-options :t/ens-terms-registration)
:options {:topBar {:title {:text (i18n/label :t/ens-terms-registration)}}
:insets {:top? true}}
:component ens/terms}
{:name :ens-name-details
@@ -135,7 +132,7 @@
:options {:insets {:top? true}}
:component ens/name-details}
{:name :blocked-users-list
:options {:topBar (topbar-options :t/blocked-users)
:options {:topBar {:title {:text (i18n/label :t/blocked-users)}}
:insets {:top? true}}
:component contacts-list/blocked-users-list}
{:name :wakuv2-settings
@@ -151,7 +148,7 @@
:options {:insets {:top? true}}
:component bootnodes-settings/bootnodes-settings}
{:name :installations
:options {:topBar (topbar-options (i18n/label :t/devices))
:options {:topBar {:title {:text (i18n/label :t/devices)}}
:insets {:top? true}}
:component pairing/installations}
{:name :edit-bootnode
@@ -167,91 +164,107 @@
:options {:insets {:top? true}}
:component edit-mailserver/edit-mailserver}
{:name :dapps-permissions
:options {:topBar (topbar-options :t/dapps-permissions)
:options {:topBar {:title {:text (i18n/label :t/dapps-permissions)}}
:insets {:top? true}}
:component dapps-permissions/dapps-permissions}
{:name :privacy-and-security
:options {:topBar (topbar-options :t/privacy-and-security)
:options {:topBar {:title {:text (i18n/label :t/privacy-and-security)}}
:insets {:top? true}}
:component privacy-and-security/privacy-and-security}
{:name :messages-from-contacts-only
:options {:topBar (topbar-options :t/accept-new-chats-from)
:options {:topBar {:title {:text (i18n/label :t/accept-new-chats-from)}}
:insets {:top? true}}
:component messages-from-contacts-only/messages-from-contacts-only-view}
{:name :appearance
:options {:topBar (topbar-options :t/appearance)
:options {:topBar {:title {:text (i18n/label :t/appearance)}}
:insets {:top? true}}
:component appearance/appearance-view}
{:name :privacy-and-security-profile-pic-show-to
:options {:topbar (topbar-options :t/show-profile-pictures-to)
:options {:topbar {:title {:text (i18n/label :t/show-profile-pictures-to)}}
:insets {:top? true}}
:component privacy-and-security/profile-pic-show-to}
{:name :privacy-and-security-profile-pic
:options {:topBar (topbar-options :t/show-profile-pictures)
:options {:topBar {:title {:text (i18n/label :t/show-profile-pictures)}}
:insets {:top? true}}
:component privacy-and-security/profile-pic}
{:name :notifications
:options {:topBar (topbar-options :t/notification-settings)
:options {:topBar {:title {:text (i18n/label :t/notification-settings)}}
:insets {:top? true}}
:component notifications-settings/notifications-settings}
{:name :sync-settings
:options {:topBar (topbar-options :t/sync-settings)
:options {:topBar {:title {:text (i18n/label :t/sync-settings)}}
:insets {:top? true}}
:component sync-settings/sync-settings}
{:name :advanced-settings
:options {:topBar (topbar-options :t/advanced)
:options {:topBar {:title {:text (i18n/label :t/advanced)}}
:insets {:top? true}}
:component advanced-settings/advanced-settings}
{:name :help-center
:options {:topBar (topbar-options :t/need-help)
:options {:topBar {:title {:text (i18n/label :t/need-help)}}
:insets {:top? true}}
:component help-center/help-center}
{:name :glossary
:options {:topBar (topbar-options :t/glossary)
:options {:topBar {:title {:text (i18n/label :t/glossary)}}
:insets {:top? true}}
:component glossary/glossary}
{:name :about-app
:options {:topBar (topbar-options :t/about-app)
:options {:topBar {:title {:text (i18n/label :t/about-app)}}
:insets {:top? true}}
:component about-app/about-app}
{:name :privacy-policy
:options {:topBar (topbar-options :t/privacy-policy)
:options {:topBar {:title {:text (i18n/label :t/privacy-policy)}}
:insets {:top? true}}
:component about-app/privacy-policy}
{:name :terms-of-service
:options {:topBar (topbar-options :t/terms-of-service)
:options {:topBar {:title {:text (i18n/label :t/terms-of-service)}}
:insets {:top? true}}
:component about-app/tos}
{:name :principles
:options {:topBar (topbar-options :t/principles)
:options {:topBar {:title {:text (i18n/label :t/principles)}}
:insets {:top? true}}
:component about-app/principles}
{:name :manage-dapps-permissions
;;TODO dynamic title
:options {:insets {:top? true}}
:component dapps-permissions/manage}
{:name :network-settings
;;TODO accessories
:options {:insets {:top? true}}
:component network/network-settings}
{:name :network-details
:options {:topBar {:title {:text (i18n/label :t/network-details)}}
:insets {:top? true}}
:component network-details/network-details}
{:name :network-info
:options {:topBar {:title {:text (i18n/label :t/network-info)}}
:insets {:top? true}}
:component network-info/network-info}
{:name :rpc-usage-info
:options {:topBar (topbar-options :t/rpc-usage-info)
:options {:topBar {:title {:text (i18n/label :t/rpc-usage-info)}}
:insets {:top? true}}
:component rpc-usage-info/usage-info}
{:name :peers-stats
:options {:topBar (topbar-options :t/peers-stats)
:options {:topBar {:title {:text (i18n/label :t/peers-stats)}}
:insets {:top? true}}
:component peers-stats/peers-stats}
{:name :edit-network
:options {:topBar {:title {:text (i18n/label :t/add-network)}}
:insets {:top? true}}
:component edit-network/edit-network}
{:name :log-level-settings
:options {:topBar (topbar-options :t/log-level-settings)
:options {:topBar {:title {:text (i18n/label :t/log-level-settings)}}
:insets {:top? true}}
:component log-level-settings/log-level-settings}
{:name :fleet-settings
:options {:topBar (topbar-options :t/fleet-settings)
:options {:topBar {:title {:text (i18n/label :t/fleet-settings)}}
:insets {:top? true}}
:component fleet-settings/fleet-settings}
{:name :mobile-network-settings
:options {:topBar (topbar-options :t/mobile-network-settings)
:options {:topBar {:title {:text (i18n/label :t/mobile-network-settings)}}
:insets {:top? true}}
:component mobile-network-settings/mobile-network-settings}
{:name :backup-settings
:options {:topBar (topbar-options :t/backup-settings)
:options {:topBar {:title {:text (i18n/label :t/backup-settings)}}
:insets {:top? true}}
:component backup-settings/backup-settings}
{:name :backup-seed
@@ -259,14 +272,14 @@
:options {:insets {:top? true}}
:component profile.seed/backup-seed}
{:name :reset-password
:options {:topBar (topbar-options :t/reset-password)
:options {:topBar {:title {:text (i18n/label :t/reset-password)}}
:insets {:top? true}}
:component reset-password/reset-password}
{:name :delete-profile
:insets {:bottom? true}
:component delete-profile/delete-profile}
{:name :default-sync-period-settings
:options {:topBar (topbar-options :t/default-sync-period)
:options {:topBar {:title {:text (i18n/label :t/default-sync-period)}}
:insets {:top? true}}
:component default-sync-period-settings/default-sync-period-settings}
@@ -275,7 +288,7 @@
;[Chat] Link preview settings
{:name :link-previews-settings
:options {:topBar (topbar-options :t/chat-link-previews)
:options {:topBar {:title {:text (i18n/label :t/chat-link-previews)}}
:insets {:top? true}}
:component link-previews-settings/link-previews-settings}
@@ -315,7 +328,7 @@
;;TODO WHY MODAL?
;[Profile] Notifications settings
{:name :notifications-settings
:options {:topBar (topbar-options :t/notification-settings)
:options {:topBar {:title {:text (i18n/label :t/notification-settings)}}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}
+1 -1
View File
@@ -2,7 +2,7 @@
(:require-macros [legacy.status-im.utils.slurp :refer [slurp]])
(:require
[legacy.status-im.fleet.default-fleet :refer (default-fleets)])
(:require [tests.test-utils :as utils.test]))
(:require [legacy.status-im.utils.test :as utils.test]))
;; to generate a js Proxy at js/__STATUS_MOBILE_JS_IDENTITY_PROXY__ that accept any (.xxx) call and
;; return itself
+5
View File
@@ -258,6 +258,11 @@
(log/debug "[native-module] set-soft-input-mode")
(.setSoftInputMode ^js (ui-helper) mode))
(defn request-local-network-access
[callback]
(log/debug "[native-module] request-local-network-access")
(.requestLocalNetworkAccess ^js (network) callback))
(defn call-rpc
[payload callback]
(log/debug "[native-module] call-rpc")
-26
View File
@@ -106,32 +106,6 @@ The convention is `:size-<number>`, e.g size `20` is `:size-20`
- Try to make all other vars private because they should almost never be used
directly.
## Default value when destructuring
Too often callers pass nil values because values can be wrapped in a `when` for example.
In this case, the default value is not applied, because :or macro will use default only when the value is absent.
Instead, use `(or (:value props) some-default-value)` in a `let` expression or as a parameter value.
```clojure
;; bad (unreliable)
(defn- view-internal
[{:keys [auto-focus?
init-value
return-key-type]
:or {auto-focus? false
init-value 0
return-key-type :done}}]
...)
;; good
(defn- view-internal
[{:keys [theme size something] :as props}]
(let [auto-focus? (or (:auto-focus? props) false)
init-value (or (:init-value props) 0)
return-key-type (or (:return-key-type props) :done)]
...))
```
## Component tests
We don't attempt to write component tests verifying how components look on the
@@ -14,26 +14,28 @@
[react-native.reanimated :as reanimated]))
(defn drag-gesture
[x-pos disabled? track-width sliding-complete?
[x-pos gestures-disabled? set-gestures-disabled disabled? track-width sliding-complete?
set-sliding-complete
on-complete reset-fn]
(-> (gesture/gesture-pan)
(gesture/with-test-ID :slide-button-gestures)
(gesture/enabled (not disabled?))
(gesture/min-distance 0)
(gesture/on-update (fn [event]
(let [x-translation (oops/oget event "translationX")
clamped-x (utils/clamp-value x-translation 0 track-width)
reached-end? (>= clamped-x track-width)]
(reanimated/set-shared-value x-pos clamped-x)
(when (and reached-end? (not sliding-complete?))
(set-sliding-complete true)
(when on-complete (on-complete reset-fn))))))
(gesture/on-end (fn [event]
(let [x-translation (oops/oget event "translationX")
reached-end? (>= x-translation track-width)]
(when (not reached-end?)
(animations/reset-track-position x-pos)))))))
(let [gestures-enabled? (not (or disabled? gestures-disabled?))]
(-> (gesture/gesture-pan)
(gesture/with-test-ID :slide-button-gestures)
(gesture/enabled gestures-enabled?)
(gesture/min-distance 0)
(gesture/on-update (fn [event]
(let [x-translation (oops/oget event "translationX")
clamped-x (utils/clamp-value x-translation 0 track-width)
reached-end? (>= clamped-x track-width)]
(reanimated/set-shared-value x-pos clamped-x)
(when (and reached-end? (not sliding-complete?))
(set-gestures-disabled true)
(set-sliding-complete true)
(when on-complete (on-complete reset-fn))))))
(gesture/on-end (fn [event]
(let [x-translation (oops/oget event "translationX")
reached-end? (>= x-translation track-width)]
(when (not reached-end?)
(animations/reset-track-position x-pos))))))))
(defn view
"Options
@@ -53,11 +55,14 @@
[track-width set-track-width] (rn/use-state nil)
[sliding-complete?
set-sliding-complete] (rn/use-state false)
[gestures-disabled?
set-gestures-disabled] (rn/use-state disabled?)
on-track-layout (rn/use-callback
#(set-track-width (oops/oget % "nativeEvent.layout.width")))
reset-fn (rn/use-callback
(fn []
(set-sliding-complete false)
(set-gestures-disabled false)
(animations/reset-track-position x-pos)))
dimensions (rn/use-callback
(partial utils/get-dimensions
@@ -72,13 +77,15 @@
[dimensions])
custom-color (if (= type :danger) :danger customization-color)
gesture (rn/use-memo #(drag-gesture x-pos
gestures-disabled?
set-gestures-disabled
disabled?
(dimensions :usable-track)
sliding-complete?
set-sliding-complete
on-complete
reset-fn)
[sliding-complete? disabled?])]
[gestures-disabled? sliding-complete? disabled?])]
[gesture/gesture-detector
{:gesture gesture}
[reanimated/view
@@ -20,8 +20,8 @@
[text/text {:size :paragraph-1 :weight :medium :number-of-lines 2} label]]])
(defn channel-actions
[{:keys [container-style actions]}]
[rn/view {:style (assoc container-style :flex-direction :row)}
[{:keys [style actions]}]
[rn/view {:style (merge {:flex-direction :row :flex 1} style)}
(map-indexed
(fn [index action]
^{:key index}
+1
View File
@@ -142,6 +142,7 @@
[color]
{:padding 12
:height 102
:flex 1
:border-radius 16
:background-color (colors/custom-color color 50 10)
:justify-content :space-between})
@@ -65,7 +65,7 @@
{:title "Title"
:type :account
:account-avatar-emoji "🍿"
:networks [{:network-name :ethereum :short-name "eth"}]
:networks [{:name :ethereum :short-name "eth"}]
:description "0x62b...0a5"
:customization-color :purple}]
theme)
@@ -76,8 +76,7 @@
(str description " · " (i18n/label :t/on-device))])
(defn- context-tag-subtitle
[{:keys [context-tag-type community-logo community-name account-name emoji customization-color
full-name profile-picture]}]
[{:keys [context-tag-type community-logo community-name account-name emoji customization-color]}]
(let [tag-type (or context-tag-type :account)]
[rn/view
{:accessibility-label :context-tag-wrapper
@@ -89,9 +88,7 @@
:community-name community-name
:community-logo community-logo
:size 24
:customization-color customization-color
:profile-picture profile-picture
:full-name full-name}]]))
:customization-color customization-color}]]))
(defn- description-subtitle
[{:keys [theme blur? description]}]
@@ -103,7 +100,7 @@
(defn- subtitle
[{:keys [type theme blur? keycard? networks description community-name community-logo
context-tag-type account-name emoji customization-color full-name profile-picture]}]
context-tag-type account-name emoji customization-color]}]
(cond
(= :keypair type)
[keypair-subtitle
@@ -131,9 +128,7 @@
:community-name community-name
:account-name account-name
:emoji emoji
:customization-color customization-color
:profile-picture profile-picture
:full-name full-name}]
:customization-color customization-color}]
(and (not= :label type) description)
[description-subtitle
@@ -192,7 +187,7 @@
on-button-press
on-button-long-press
button-disabled? account-avatar-emoji account-avatar-type customization-color icon-avatar
profile-picture keycard? networks label full-name]}]
profile-picture keycard? networks label]}]
[rn/view {:style style/container}
(when (left-image-supported-types type)
[rn/view {:style style/left-container}
@@ -223,9 +218,7 @@
:context-tag-type context-tag-type
:customization-color customization-color
:account-name account-name
:emoji emoji
:full-name full-name
:profile-picture profile-picture}]]
:emoji emoji}]]
[right-icon
{:theme theme
:type type
@@ -2,10 +2,5 @@
(defn root-container
[opacity height]
{:height (or height 252)
:opacity opacity
:position :absolute
:top 0
:left 0
:right 0
:z-index -1})
{:height (or height 252)
:opacity opacity})
@@ -3,7 +3,7 @@
[quo.foundations.colors :as colors]))
(def ^:private themes
{:light {:default {:bg colors/neutral-5
{:light {:default {:bg colors/white
:border colors/neutral-20
:icon colors/neutral-50
:text colors/neutral-100}
+3 -4
View File
@@ -55,17 +55,16 @@
(def ^:private custom-props
"Custom properties that must be removed from properties map passed to InputText."
[:type :blur? :error? :right-icon :left-icon :disabled? :small? :button
[:type :blur? :theme :error? :right-icon :left-icon :disabled? :small? :button
:label :char-limit :on-char-limit-reach :icon-name :multiline? :on-focus :on-blur
:container-style])
(defn- base-input
[{:keys [blur? error? right-icon left-icon disabled? small? button
[{:keys [blur? theme error? right-icon left-icon disabled? small? button
label char-limit multiline? clearable? on-focus on-blur container-style
on-change-text on-char-limit-reach weight default-value]
:as props}]
(let [theme (quo.theme/use-theme-value)
[status set-status] (rn/use-state :default)
(let [[status set-status] (rn/use-state :default)
internal-on-focus (rn/use-callback #(set-status :focus))
internal-on-blur (rn/use-callback #(set-status :default))
[multiple-lines?
@@ -10,7 +10,7 @@
:background-color (colors/theme-colors colors/neutral-10
colors/neutral-80-opa-80
theme)
:min-height 40
:height 40
:padding-horizontal 12
:padding-vertical 9
:margin-top 4})
@@ -22,12 +22,11 @@
(defn- locked-input-internal
[{:keys [label icon container-style theme]} value]
[rn/view {:style container-style}
(when label
[text/text
{:size :paragraph-2
:weight :medium
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
label])
[text/text
{:size :paragraph-2
:weight :medium
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
label]
[info-box
{:theme theme
:icon icon
@@ -60,8 +60,8 @@
:padding-horizontal 12
:padding-top 10
:padding-bottom 12
:width (if (= :message size) 295 335)
:flex 1})
:height (if (= :message size) 245 266)
:width (if (= :message size) 295 335)})
(def header-container
{:flex-direction :row
@@ -7,6 +7,7 @@
{:title "Some title"
:description "Some description"
:link "status.im"
:logo "data:image/png,logo-x"
:thumbnail "data:image/png,whatever"})
(h/describe "Links - Link Preview"
@@ -20,12 +21,17 @@
(h/is-truthy (h/query-by-text (:title props)))
(h/is-truthy (h/query-by-text (:description props)))
(h/is-truthy (h/query-by-text (:link props)))
(h/is-truthy (h/query-by-label-text :logo))
(h/is-truthy (h/query-by-label-text :thumbnail)))
(h/test "does not render thumbnail if prop is not present"
(h/render [view/view (dissoc props :thumbnail)])
(h/is-null (h/query-by-label-text :thumbnail)))
(h/test "does not render logo if prop is not present"
(h/render [view/view (dissoc props :logo)])
(h/is-null (h/query-by-label-text :logo)))
(h/test "shows button to enable preview when preview is disabled"
(h/render [view/view
(assoc props
@@ -1,14 +1,9 @@
(ns quo.components.links.link-preview.view
(:require
["react-native-blob-util" :default ReactNativeBlobUtil]
[oops.core :as oops]
[quo.components.buttons.button.view :as button]
[quo.components.links.link-preview.style :as style]
[quo.components.markdown.text :as text]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.svg :as svg]
[taoensso.timbre :as log]))
[react-native.core :as rn]))
(defn- button-disabled
[disabled-text on-enable]
@@ -56,30 +51,14 @@
thumbnail)
:accessibility-label :thumbnail}])
(defn- get-image-data
[logo set-is-svg on-success]
(-> (.config ReactNativeBlobUtil (clj->js {:trusty platform/ios?}))
(.fetch "GET" logo)
(.then (fn [imgObj]
(set-is-svg (= "image/svg"
(oops/oget imgObj
["respInfo" "headers" "Content-Type"])))
(on-success (oops/oget imgObj "data"))))
(.catch #(log/error "could not fetch favicon " logo))))
(defn- logo-comp
[logo]
(let [[image-data set-image-data] (rn/use-state nil)
[is-svg? set-is-svg] (rn/use-state nil)
on-success (fn [data-uri]
(set-image-data data-uri))
_get-image-data (get-image-data logo set-is-svg on-success)]
(if is-svg?
[svg/svg-xml (merge style/logo {:xml image-data})]
[rn/image
{:accessibility-label :logo
:source {:uri (str "data:image/png;base64," image-data)}
:style style/logo}])))
[rn/image
{:accessibility-label :logo
:source (if (string? logo)
{:uri logo}
logo)
:style style/logo}])
(defn view
[{:keys [title logo description link thumbnail
@@ -21,7 +21,6 @@
:border-radius 12
:border-style :dashed
:align-items :center
:flex-direction :row
:justify-content :center
:align-self :stretch
:padding horizontal-padding
+8 -27
View File
@@ -1,31 +1,19 @@
(ns quo.components.links.url-preview.view
(:require
[clojure.string :as string]
[quo.components.icon :as icon]
[quo.components.links.url-preview.style :as style]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.svg :as svg]))
(def base64-svg-prefix "data:image/svg;base64,")
[react-native.core :as rn]))
(defn- logo-comp
[{:keys [logo]}]
(if (and (string? logo)
(string/starts-with? logo base64-svg-prefix))
[svg/svg-xml
(merge style/logo
{:xml (-> logo
(string/replace base64-svg-prefix "")
js/atob)})]
[rn/image
{:accessibility-label :logo
:source (if (string? logo)
{:uri logo}
logo)
:style style/logo
:resize-mode :cover}]))
[rn/image
{:accessibility-label :logo
:source (if (string? logo)
{:uri logo}
logo)
:style style/logo}])
(defn- content
[{:keys [title body]}]
@@ -62,10 +50,6 @@
[rn/view
{:accessibility-label :url-preview-loading
:style (merge (style/loading-container) container-style)}
[icon/icon :i/loading
{:size 12
:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:container-style {:margin-right 8}}]
[rn/text
{:size :paragraph-2
:weight :medium
@@ -76,9 +60,6 @@
{:accessibility-label :url-preview
:style (merge (style/container) container-style)}
(when logo
[logo-comp
{:logo (if (map? logo)
(:data-uri logo)
logo)}])
[logo-comp {:logo logo}])
[content {:title title :body body}]
[clear-button {:on-press on-clear}]]))
@@ -32,7 +32,7 @@
[rn/view {:style style/url-preview-separator}])
(defn- item-component
[{:keys [title body loading? logo url]} _ _
[{:keys [title body loading? logo]} _ _
{:keys [width on-clear loading-message container-style]}]
[url-preview/view
{:logo logo
@@ -41,8 +41,7 @@
:loading? loading?
:loading-message loading-message
:on-clear on-clear
:container-style (merge container-style {:width width})
:url url}])
:container-style (merge container-style {:width width})}])
(defn- f-view
[]
@@ -6,101 +6,71 @@
(def ^:private theme :light)
(def ^:private default-props
{:account-props {:name "Account name"
:address "0x1234567890"
:emoji "🍑"
:customization-color "blue"}})
(defn- with-defaults
([] default-props)
([props] (merge default-props props)))
(h/describe "List items: account"
(h/test "default render'"
(h/render-with-theme-provider [account/view (with-defaults)] theme)
(h/render-with-theme-provider [account/view] theme)
(h/is-truthy (h/query-by-label-text :container)))
(h/test "on-press-in changes state to :pressed"
(h/render-with-theme-provider [account/view (with-defaults)] theme)
(h/render-with-theme-provider [account/view] theme)
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
{:backgroundColor (colors/resolve-color :blue :light 5)})))
(h/test "on-press-in changes state to :pressed with blur? enabled"
(h/render-with-theme-provider [account/view (with-defaults {:blur? true})] theme)
(h/render-with-theme-provider [account/view {:blur? true}] theme)
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
{:backgroundColor colors/white-opa-5})))
(h/test "render with state :active"
(h/render-with-theme-provider [account/view (with-defaults {:state :active})] theme)
(h/render-with-theme-provider [account/view {:state :active}] theme)
(h/has-style (h/query-by-label-text :container)
{:backgroundColor (colors/resolve-color :blue :light 10)}))
(h/test "render with state :active and blur? enabled"
(h/render-with-theme-provider [account/view
(with-defaults
{:blur? true
:state :active})]
{:blur? true
:state :active}]
theme)
(h/has-style (h/query-by-label-text :container)
{:backgroundColor colors/white-opa-10}))
(h/test "render with state :selected"
(h/render-with-theme-provider [account/view (with-defaults {:state :selected})] theme)
(h/render-with-theme-provider [account/view {:state :selected}] theme)
(h/is-truthy (h/query-by-label-text :check-icon)))
(h/test "calls on-press"
(let [on-press (h/mock-fn)]
(h/render-with-theme-provider [account/view (with-defaults {:on-press on-press})] theme)
(h/render-with-theme-provider [account/view {:on-press on-press}] theme)
(h/fire-event :on-press (h/get-by-label-text :container))
(h/was-called on-press)))
(h/test "renders token props if type :tag"
(h/render-with-theme-provider [account/view
(with-defaults {:type :tag
:token-props {:symbol "SNT"
:value "1,000"}})]
theme)
(h/render-with-theme-provider [account/view {:type :tag}] theme)
(h/is-truthy (h/query-by-label-text :tag-container)))
(h/test "renders keycard icon if title-icon is present"
(h/render-with-theme-provider [account/view (with-defaults {:title-icon :i/placeholder})] theme)
(h/render-with-theme-provider [account/view {:title-icon :i/placeholder}] theme)
(h/is-truthy (h/query-by-label-text :title-icon)))
(h/test "doesn't render keycard icon if title-icon is missing"
(h/render-with-theme-provider [account/view (with-defaults)] theme)
(h/render-with-theme-provider [account/view] theme)
(h/is-falsy (h/query-by-label-text :title-icon)))
(h/test "renders balance container but not arrow icon if type :balance-neutral"
(h/render-with-theme-provider [account/view
(with-defaults {:type :balance-neutral
:balance-props {:fiat-value "€0.00"
:percentage-change "0.0"
:fiat-change "€0.00"}})]
theme)
(h/render-with-theme-provider [account/view {:type :balance-neutral}] theme)
(h/is-truthy (h/query-by-label-text :balance-container))
(h/is-falsy (h/query-by-label-text :arrow-icon)))
(h/test "renders balance container and negative arrow icon if type :balance-negative"
(h/render-with-theme-provider [account/view
(with-defaults {:type :balance-negative
:balance-props {:fiat-value "€0.00"
:percentage-change "0.0"
:fiat-change "€0.00"}})]
theme)
(h/render-with-theme-provider [account/view {:type :balance-negative}] theme)
(h/is-truthy (h/query-by-label-text :balance-container))
(h/is-truthy (h/query-by-label-text :icon-negative))
(h/is-falsy (h/query-by-label-text :icon-positive)))
(h/test "renders balance container and positive arrow icon if type :balance-positive"
(h/render-with-theme-provider [account/view
(with-defaults {:type :balance-positive
:balance-props {:fiat-value "€0.00"
:percentage-change "0.0"
:fiat-change "€0.00"}})]
theme)
(h/render-with-theme-provider [account/view {:type :balance-positive}] theme)
(h/is-truthy (h/query-by-label-text :balance-container))
(h/is-falsy (h/query-by-label-text :icon-negative))
(h/is-truthy (h/query-by-label-text :icon-positive)))
@@ -108,9 +78,8 @@
(h/test "renders options button if type :action"
(let [on-options-press (h/mock-fn)]
(h/render-with-theme-provider [account/view
(with-defaults
{:type :action
:on-options-press on-options-press})]
{:type :action
:on-options-press on-options-press}]
theme)
(h/is-truthy (h/query-by-label-text :options-button))
(h/fire-event :on-press (h/get-by-label-text :options-button))
@@ -1,49 +0,0 @@
(ns quo.components.list-items.account.schema)
(def ^:private ?balance
[:map
[:balance-props
[:map
[:fiat-value :string]
[:percentage-change :string]
[:fiat-change :string]]]])
(def ^:private ?tag
[:map
[:token-props
[:map
[:symbol :string]
[:value :string]]]])
(def ^:private ?action
[:map
[:on-options-press {:optional true} [:maybe fn?]]])
(def ^:private ?base
[:map
[:type {:optional true}
[:enum :default :tag :action :balance-neutral :balance-negative :balance-positive]]
[:state {:optional true} [:enum :default :selected :active]]
[:blur? {:optional true} [:maybe :boolean]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:on-press {:optional true} [:maybe fn?]]
[:title-icon {:optional true} [:maybe :keyword]]
[:account-props
[:map
[:name :string]
[:address :string]
[:emoji :string]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]])
(def ?schema
[:=>
[:cat
[:multi {:dispatch :type}
[:balance-neutral [:merge ?base ?balance]]
[:balance-negative [:merge ?base ?balance]]
[:balance-positive [:merge ?base ?balance]]
[:tag [:merge ?base ?tag]]
[:action [:merge ?base ?action]]
[:default ?base]
[:malli.core/default ?base]]]
:any])
+33 -35
View File
@@ -2,14 +2,13 @@
(:require
[quo.components.avatars.account-avatar.view :as account-avatar]
[quo.components.icon :as icon]
[quo.components.list-items.account.schema :as component-schema]
[quo.components.list-items.account.style :as style]
[quo.components.markdown.text :as text]
[quo.components.wallet.address-text.view :as address-text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
[reagent.core :as reagent]))
(defn- account-view
[{:keys [account-props title-icon blur? theme]}]
@@ -96,40 +95,39 @@
(colors/resolve-color customization-color theme))}]])
(defn- internal-view
[{:keys [type state blur? customization-color on-press]
:or {customization-color :blue
type :default
state :default
blur? false}
:as props}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))
props (assoc props :theme theme)]
[rn/pressable
{:style (style/container
{:state state
:blur? blur?
:customization-color customization-color
:pressed? pressed?})
:on-press-in on-press-in
:on-press on-press
:on-press-out on-press-out
:accessibility-label :container}
[account-view props]
[rn/view {:style (when (= type :tag) style/token-tag-container)}
(cond
(#{:balance-neutral :balance-negative :balance-positive} type)
[balance-view props]
[_]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)]
(fn [{:keys [type state blur? customization-color on-press]
:or {customization-color :blue
type :default
state :default
blur? false}
:as props}]
[rn/pressable
{:style (style/container
{:state state
:blur? blur?
:customization-color customization-color
:pressed? @pressed?})
:on-press-in on-press-in
:on-press on-press
:on-press-out on-press-out
:accessibility-label :container}
[account-view props]
[rn/view {:style (when (= type :tag) style/token-tag-container)}
(cond
(#{:balance-neutral :balance-negative :balance-positive} type)
[balance-view props]
(= type :tag)
[token-tag props]
(= type :tag)
[token-tag props]
(= type :action)
[options-button props]
(= type :action)
[options-button props]
(and (= type :default) (= state :selected))
[check-icon props])]]))
(and (= type :default) (= state :selected))
[check-icon props])]])))
(def view (schema/instrument #'internal-view component-schema/?schema))
(def view (quo.theme/with-theme internal-view))
@@ -13,9 +13,9 @@
(h/describe "List items: account list card"
(h/test "Test icon renders for ':action :icon'"
(h/render-with-theme-provider [account-list-card/view
{:account-props account-props
:network :ethereum
:action :icon
:on-options-press (fn [])}])
(h/render [account-list-card/view
{:account-props account-props
:network :ethereum
:state :default
:action :icon}])
(h/is-truthy (h/get-by-label-text :icon))))
@@ -1,29 +0,0 @@
(ns quo.components.list-items.account-list-card.schema)
(def ^:private ?base
[:map
[:action {:optional true} [:enum :icon :none]]
[:blur? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]
[:account-props
[:map {:closed true}
[:type [:enum :default :watch-only]]
[:name :string]
[:address :string]
[:emoji :string]
[:size {:optional true} [:enum 80 :size-64 48 32 28 24 20 16]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]
[:networks {:optional true} [:* [:map [:network-name :keyword] [:short-name :string]]]]])
(def ^:private ?on-option-press
[:map
[:on-options-press [:maybe fn?]]])
(def ?schema
[:=>
[:cat
[:multi {:dispatch :action}
[:icon [:merge ?base ?on-option-press]]
[:none ?base]
[:malli.core/default ?base]]]
:any])
@@ -2,44 +2,41 @@
(:require
[quo.components.avatars.account-avatar.view :as account-avatar]
[quo.components.icon :as icon]
[quo.components.list-items.account-list-card.schema :as component-schema]
[quo.components.list-items.account-list-card.style :as style]
[quo.components.markdown.text :as text]
[quo.components.wallet.address-text.view :as address-text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
[reagent.core :as reagent]))
(defn- internal-view
[{:keys [action blur? account-props networks on-press on-options-press]}]
(let [theme (quo.theme/use-theme-value)
[state set-state] (rn/use-state :default)
on-press-in (rn/use-callback #(set-state :pressed))
on-press-out (rn/use-callback #(set-state :default))]
[rn/pressable
{:style (style/container {:state state :blur? blur? :theme theme})
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :container}
[rn/view {:style style/left-container}
[account-avatar/view account-props]
[rn/view {:style {:margin-left 8}}
[text/text
{:weight :semi-bold
:size :paragraph-2}
(:name account-props)]
[address-text/view
{:networks networks
:address (:address account-props)
:format :short}]]]
(when (= action :icon)
[rn/pressable {:on-press on-options-press}
[icon/icon :i/options
{:color (if blur?
colors/white-opa-70
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))
:accessibility-label :icon}]])]))
[]
(let [state (reagent/atom :default)]
(fn [{:keys [action blur? account-props networks on-press on-options-press theme]}]
[rn/pressable
{:style (style/container {:state @state :blur? blur? :theme theme})
:on-press-in #(reset! state :pressed)
:on-press-out #(reset! state :default)
:on-press on-press
:accessibility-label :container}
[rn/view {:style style/left-container}
[account-avatar/view account-props]
[rn/view {:style {:margin-left 8}}
[text/text
{:weight :semi-bold
:size :paragraph-2}
(:name account-props)]
[address-text/view
{:networks networks
:address (:address account-props)
:format :short}]]]
(when (= action :icon)
[rn/pressable {:on-press on-options-press}
[icon/icon :i/options
{:color (if blur?
colors/white-opa-70
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))
:accessibility-label :icon}]])])))
(def view (schema/instrument #'internal-view component-schema/?schema))
(def view (quo.theme/with-theme internal-view))
@@ -4,36 +4,32 @@
[quo.foundations.colors :as colors]
[test-helpers.component :as h]))
(defn- with-defaults
([] (with-defaults {}))
([props] (merge {:address "0x0ah...78b"} props)))
(h/describe "List items: address"
(h/test "default render"
(h/render-with-theme-provider [address/view (with-defaults)])
(h/render [address/view])
(h/is-truthy (h/query-by-label-text :container)))
(h/test "on-press-in changes state to :pressed"
(h/render-with-theme-provider [address/view (with-defaults)])
(h/render [address/view])
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
{:backgroundColor (colors/custom-color :blue 50 5)})))
(h/test "on-press-in changes state to :pressed with blur? enabled"
(h/render-with-theme-provider [address/view (with-defaults {:blur? true})])
(h/render [address/view {:blur? true}])
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
{:backgroundColor colors/white-opa-5})))
(h/test "on-press-out changes state to :active"
(h/render-with-theme-provider [address/view (with-defaults {:active-state? true})])
(h/render [address/view {:active-state? true}])
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/fire-event :on-press-out (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
{:backgroundColor (colors/custom-color :blue 50 10)})))
(h/test "on-press-out changes state to :active with blur? enabled"
(h/render-with-theme-provider [address/view (with-defaults {:active-state? true :blur? true})])
(h/render [address/view {:active-state? true :blur? true}])
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/fire-event :on-press-out (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
@@ -41,6 +37,6 @@
(h/test "on-press event is called"
(let [on-press (h/mock-fn)]
(h/render-with-theme-provider [address/view (with-defaults {:on-press on-press})])
(h/render [address/view {:on-press on-press}])
(h/fire-event :on-press (h/get-by-label-text :container))
(h/was-called on-press))))
@@ -1,13 +0,0 @@
(ns quo.components.list-items.address.schema)
(def ?schema
[:=>
[:cat
[:map
[:address :string]
[:networks {:optional true} [:* [:map [:network-name :keyword] [:short-name :string]]]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:on-press {:optional true} [:maybe fn?]]
[:blur? {:optional true} [:maybe :boolean]]
[:active-state? {:optional true} [:maybe :boolean]]]]
:any])
+28 -33
View File
@@ -1,13 +1,12 @@
(ns quo.components.list-items.address.view
(:require
[quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
[quo.components.list-items.address.schema :as component-schema]
[quo.components.list-items.address.style :as style]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[schema.core :as schema]
[reagent.core :as reagent]
[utils.address :as address]))
(defn- left-container
@@ -38,35 +37,31 @@
(address/get-shortened-key address)]]]])
(defn- internal-view
[{:keys [networks address customization-color on-press active-state? blur?]
:or {customization-color :blue}}]
(let [theme (quo.theme/use-theme-value)
[state set-state] (rn/use-state :default)
active? (rn/use-ref-atom false)
timer (rn/use-ref-atom nil)
on-press-in (rn/use-callback
(fn []
(when-not (= state :selected)
(reset! timer (js/setTimeout #(set-state :pressed) 100))))
[state])
on-press-out (rn/use-callback
(fn []
(let [new-state (if (or (not active-state?) @active?) :default :active)]
(when @timer (js/clearTimeout @timer))
(reset! timer nil)
(reset! active? (= new-state :active))
(set-state new-state)))
[active-state?])]
[rn/pressable
{:style (style/container state customization-color blur?)
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :container}
[left-container
{:theme theme
:networks networks
:address address
:blur? blur?}]]))
[]
(let [state (reagent/atom :default)
active? (atom false)
timer (atom nil)
on-press-in (fn []
(when-not (= @state :selected)
(reset! timer (js/setTimeout #(reset! state :pressed) 100))))]
(fn [{:keys [networks address customization-color on-press active-state? blur? theme]
:or {customization-color :blue}}]
(let [on-press-out (fn []
(let [new-state (if (or (not active-state?) @active?) :default :active)]
(when @timer (js/clearTimeout @timer))
(reset! timer nil)
(reset! active? (= new-state :active))
(reset! state new-state)))]
[rn/pressable
{:style (style/container @state customization-color blur?)
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :container}
[left-container
{:theme theme
:networks networks
:address address
:blur? blur?}]]))))
(def view (schema/instrument #'internal-view component-schema/?schema))
(def view (quo.theme/with-theme internal-view))
+39 -37
View File
@@ -5,10 +5,11 @@
[quo.components.icon :as quo.icons]
[quo.components.list-items.channel.style :as style]
[quo.components.markdown.text :as quo.text]
[quo.theme]
[react-native.core :as rn]))
[quo.theme :as theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn view
(defn- view-internal
"Options:
- notification - (nil/:notification/:mention/:mute, default: nil):
- :notification - Display a grey dot.
@@ -26,37 +27,38 @@
- on-press - (function, default: nil) - Function called when the component is pressed.
- on-long-press - (function, default: nil) - Function called when the component is long pressed.
- theme - Theme value from with-theme HOC"
[{:keys [notification locked? mentions-count customization-color emoji name on-press on-long-press]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:style (style/container pressed? customization-color theme)
:accessibility-label :channel-list-item
:on-press on-press
:on-long-press on-long-press
:on-press-in on-press-in
:on-press-out on-press-out}
[channel-avatar/view
{:size :size-32
:locked? locked?
:full-name name
:customization-color customization-color
:emoji emoji}]
[quo.text/text
{:style (style/label notification theme)
:weight :medium
:size :paragraph-1} (str "# " name)]
(when-not locked?
(condp = notification
:mute [quo.icons/icon :i/muted
{:color (style/mute-notification-icon-color theme)}]
:mention [counter/view
{:customization-color customization-color
:container-style (style/counter mentions-count)}
mentions-count]
:notification [quo.icons/icon :i/notification
{:color (style/mute-notification-icon-color theme)
:accessibility-label :unviewed-messages-public}]
nil))]))
[]
(let [pressed? (reagent/atom false)]
(fn [{:keys [notification locked? mentions-count customization-color emoji name on-press
on-long-press theme]}]
[rn/pressable
{:style (style/container @pressed? customization-color theme)
:accessibility-label :channel-list-item
:on-press on-press
:on-long-press on-long-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)}
[channel-avatar/view
{:size :size-32
:locked? locked?
:full-name name
:customization-color customization-color
:emoji emoji}]
[quo.text/text
{:style (style/label notification theme)
:weight :medium
:size :paragraph-1} (str "# " name)]
(when-not locked?
(condp = notification
:mute [quo.icons/icon :i/muted
{:color (style/mute-notification-icon-color theme)}]
:mention [counter/view
{:customization-color customization-color
:container-style (style/counter mentions-count)}
mentions-count]
:notification [quo.icons/icon :i/notification
{:color (style/mute-notification-icon-color theme)
:accessibility-label :unviewed-messages-public}]
nil))])))
(def view (theme/with-theme view-internal))
@@ -7,7 +7,8 @@
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]))
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn- logo-component
[logo]
@@ -82,7 +83,7 @@
:style {:margin-left 10}}
component])))
(defn view
(defn- view-internal
"Options:
:type - :discover/engage/share
@@ -115,48 +116,49 @@
:unread-count - number - When the info is :mention, it will be used to show
the number of unread mentions.
"
[{:keys [members type info tokens locked? title subtitle logo blur? customization-color
on-press on-long-press on-press-info container-style unread-count]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:accessibility-label :container
[]
(let [pressed? (reagent/atom false)]
(fn [{:keys [members type info tokens locked? title subtitle
logo blur? customization-color
on-press on-long-press on-press-info
container-style unread-count theme]}]
[rn/pressable
{:accessibility-label :container
:on-press-in (fn [] (reset! pressed? true))
:on-press on-press
:on-long-press on-long-press
:on-press-out (fn [] (reset! pressed? false))
:style (merge (style/container {:blur? blur?
:customization-color customization-color
:info info
:type type
:pressed? @pressed?
:theme theme})
container-style)}
[logo-component logo]
[rn/view {:style {:flex 1}}
[title-component
{:blur? blur?
:info info
:theme theme
:title title
:type type}]
(when (and (= type :share) subtitle)
[subtitle-component subtitle blur? theme])
(when (and members (= type :discover))
[community-view/community-stats-column
{:type :list-view
:members-count (:members-count members)
:active-count (:active-count members)}])]
[info-component
{:blur? blur?
:customization-color customization-color
:info info
:type type
:locked? locked?
:on-press-info on-press-info
:theme theme
:tokens tokens
:unread-count unread-count}]])))
:on-press on-press
:on-long-press on-long-press
:on-press-in on-press-in
:on-press-out on-press-out
:style (merge (style/container {:blur? blur?
:customization-color customization-color
:info info
:type type
:pressed? pressed?
:theme theme})
container-style)}
[logo-component logo]
[rn/view {:style {:flex 1}}
[title-component
{:blur? blur?
:info info
:theme theme
:title title
:type type}]
(when (and (= type :share) subtitle)
[subtitle-component subtitle blur? theme])
(when (and members (= type :discover))
[community-view/community-stats-column
{:type :list-view
:members-count (:members-count members)
:active-count (:active-count members)}])]
[info-component
{:blur? blur?
:customization-color customization-color
:info info
:type type
:locked? locked?
:on-press-info on-press-info
:theme theme
:tokens tokens
:unread-count unread-count}]]))
(def view (quo.theme/with-theme view-internal))
+39 -37
View File
@@ -6,41 +6,43 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.fast-image :as fast-image]))
[react-native.fast-image :as fast-image]
[reagent.core :as reagent]))
(defn view
[{:keys [dapp action on-press on-press-icon] :as props}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:style (style/container (assoc props :pressed? pressed?))
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out}
[rn/view {:style style/container-info}
[fast-image/fast-image
{:source (:avatar dapp)
:style {:width 32 :height 32}}]
[rn/view {:style style/user-info}
[text/text
{:weight :semi-bold
:size :paragraph-1
:style (style/style-text-name theme)}
(:name dapp)]
[text/text
{:weight :regular
:size :paragraph-2
:style (style/style-text-value theme)}
(:value dapp)]]]
(when (= action :icon)
[rn/pressable
{:on-press on-press-icon
:testID "dapp-component-icon"}
[icons/icon :i/options
{:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)
:accessibility-label :icon}]])]))
(defn- view-internal
[]
(let [pressed? (reagent/atom false)]
(fn [{:keys [dapp action on-press on-press-icon theme] :as props}]
[rn/pressable
{:style (style/container (assoc props :pressed? @pressed?))
:on-press on-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)}
[rn/view {:style style/container-info}
[fast-image/fast-image
{:source (:avatar dapp)
:style {:width 32 :height 32}}]
[rn/view {:style style/user-info}
[text/text
{:weight :semi-bold
:size :paragraph-1
:style (style/style-text-name theme)}
(:name dapp)]
[text/text
{:weight :regular
:size :paragraph-2
:style (style/style-text-value theme)}
(:value dapp)]]]
(when (= action :icon)
[rn/pressable
{:on-press on-press-icon
:testID "dapp-component-icon"}
[icons/icon :i/options
{:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)
:accessibility-label :icon}]])])))
(def view
(quo.theme/with-theme view-internal))
@@ -5,6 +5,7 @@
[quo.components.markdown.text :as text]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- info
@@ -21,19 +22,18 @@
(if (string/blank? label) "-" label)]]])
(defn- values
[{:keys [token-value fiat-value]}]
(let [theme (quo.theme/use-theme-value)]
[rn/view {:style style/values-container}
[text/text
{:weight :medium
:size :paragraph-2
:number-of-lines 1}
token-value]
[text/text
{:style (style/fiat-value theme)
:size :paragraph-2
:number-of-lines 1}
fiat-value]]))
[{:keys [token-value fiat-value theme]}]
[rn/view {:style style/values-container}
[text/text
{:weight :medium
:size :paragraph-2
:number-of-lines 1}
token-value]
[text/text
{:style (style/fiat-value theme)
:size :paragraph-2
:number-of-lines 1}
fiat-value]])
(def ?schema
[:=>
@@ -51,21 +51,26 @@
:any])
(defn- view-internal
[{:keys [on-press state customization-color]
:as props
:or {customization-color :blue}}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))
internal-state (if pressed? :pressed state)]
[rn/pressable
{:style (style/container internal-state customization-color theme)
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :network-list}
[info props]
[values props]]))
[]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)]
(fn [{:keys [on-press state customization-color theme]
:as props
:or {customization-color :blue}}]
(let [internal-state (if @pressed?
:pressed
state)]
(def view (schema/instrument #'view-internal ?schema))
[rn/pressable
{:style (style/container internal-state customization-color theme)
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :network-list}
[info props]
[values props]]))))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal ?schema)))
@@ -7,69 +7,70 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.address :as address]))
(defn- left-container
[{:keys [blur? name ens address customization-color]}]
(let [theme (quo.theme/use-theme-value)]
[rn/view {:style style/left-container}
[wallet-user-avatar/wallet-user-avatar
{:size :size-32
:full-name name
:customization-color customization-color}]
[rn/view {:style style/account-container}
[text/text
{:weight :semi-bold
:size :paragraph-1
:style style/name-text}
name]
[text/text {:size :paragraph-2}
[text/text
{:size :paragraph-2
:weight :monospace
:style (style/account-address blur? theme)}
(or ens (address/get-shortened-key address))]]]]))
[{:keys [blur? theme name ens address customization-color]}]
[rn/view {:style style/left-container}
[wallet-user-avatar/wallet-user-avatar
{:size :size-32
:full-name name
:customization-color customization-color}]
[rn/view {:style style/account-container}
[text/text
{:weight :semi-bold
:size :paragraph-1
:style style/name-text}
name]
[text/text {:size :paragraph-2}
[text/text
{:size :paragraph-2
:weight :monospace
:style (style/account-address blur? theme)}
(or ens (address/get-shortened-key address))]]]])
(defn view
[{:keys [blur? user-props active-state? customization-color on-press on-options-press]
:or {customization-color :blue
blur? false}}]
(let [theme (quo.theme/use-theme-value)
[state set-state] (rn/use-state :default)
active? (rn/use-ref-atom false)
timer (rn/use-ref-atom nil)
on-press-in (rn/use-callback
(fn []
(when-not (= state :selected)
(reset! timer (js/setTimeout #(set-state :pressed) 100))))
[state])
on-press-out (rn/use-callback
(fn []
(let [new-state (if (or (not active-state?) @active?) :default :active)]
(when @timer (js/clearTimeout @timer))
(reset! timer nil)
(reset! active? (= new-state :active))
(set-state new-state)))
[active-state?])]
[rn/pressable
{:style (style/container
{:state state :blur? blur? :customization-color customization-color})
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :container}
[left-container
{:blur? blur?
:theme theme
:name (:name user-props)
:ens (:ens user-props)
:address (:address user-props)
:customization-color (or (:customization-color user-props) :blue)}]
(when on-options-press
[rn/pressable
{:accessibility-label :options-button
:on-press on-options-press}
[icon/icon :i/options
{:color (if blur?
colors/white-opa-70
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}]])]))
(defn- internal-view
[]
(let [state (reagent/atom :default)
active? (atom false)
timer (atom nil)
on-press-in (fn []
(when-not (= @state :selected)
(reset! timer (js/setTimeout #(reset! state :pressed) 100))))]
(fn [{:keys [blur? user-props active-state? customization-color
on-press
on-options-press
theme]
:or {customization-color :blue
blur? false}}]
(let [on-press-out (fn []
(let [new-state (if (or (not active-state?) @active?) :default :active)]
(when @timer (js/clearTimeout @timer))
(reset! timer nil)
(reset! active? (= new-state :active))
(reset! state new-state)))]
[rn/pressable
{:style (style/container
{:state @state :blur? blur? :customization-color customization-color})
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :container}
[left-container
{:blur? blur?
:theme theme
:name (:name user-props)
:ens (:ens user-props)
:address (:address user-props)
:customization-color (or (:customization-color user-props) :blue)}]
(when on-options-press
[rn/pressable
{:accessibility-label :options-button
:on-press on-options-press}
[icon/icon :i/options
{:color (if blur?
colors/white-opa-70
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}]])]))))
(def view (quo.theme/with-theme internal-view))
@@ -4,10 +4,6 @@
[quo.foundations.colors :as colors]
[test-helpers.component :as h]))
(defn- with-defaults
([] (with-defaults {}))
([props] (merge {:contact-props {:full-name "Thomas Anderson"}} props)))
(def account
{:name "New House"
:address "0x21a...49e"
@@ -16,33 +12,25 @@
(h/describe "List items: saved contact address"
(h/test "default render"
(h/render-with-theme-provider [saved-contact-address/view (with-defaults)])
(h/render-with-theme-provider [saved-contact-address/view])
(h/is-truthy (h/query-by-label-text :container)))
(h/test "renders account detail when passing one account"
(h/render-with-theme-provider [saved-contact-address/view
(with-defaults
{:accounts (repeat 1 account)})])
(h/render-with-theme-provider [saved-contact-address/view {:accounts (repeat 1 account)}])
(h/is-truthy (h/query-by-label-text :account-container)))
(h/test "renders account count when passing multiple accounts"
(h/render-with-theme-provider [saved-contact-address/view
(with-defaults
{:accounts (repeat 2 account)})])
(h/render-with-theme-provider [saved-contact-address/view {:accounts (repeat 2 account)}])
(h/is-truthy (h/query-by-label-text :accounts-count)))
(h/test "on-press-in changes state to :pressed"
(h/render-with-theme-provider [saved-contact-address/view
(with-defaults
{:accounts (repeat 1 account)})])
(h/render-with-theme-provider [saved-contact-address/view {:accounts (repeat 1 account)}])
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
{:backgroundColor (colors/custom-color :blue 50 5)})))
(h/test "on-press-out changes state to :active if active-state? is true (default value)"
(h/render-with-theme-provider [saved-contact-address/view
(with-defaults
{:accounts (repeat 1 account)})])
(h/render-with-theme-provider [saved-contact-address/view {:accounts (repeat 1 account)}])
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/fire-event :on-press-out (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
@@ -50,9 +38,8 @@
(h/test "on-press-out changes state to :default if active-state? is false"
(h/render-with-theme-provider [saved-contact-address/view
(with-defaults
{:accounts (repeat 1 account)
:active-state? false})])
{:accounts (repeat 1 account)
:active-state? false}])
(h/fire-event :on-press-in (h/get-by-label-text :container))
(h/fire-event :on-press-out (h/get-by-label-text :container))
(h/wait-for #(h/has-style (h/query-by-label-text :container)
@@ -60,9 +47,9 @@
(h/test "on-press calls on-press"
(let [on-press (h/mock-fn)]
(h/render-with-theme-provider [saved-contact-address/view
(with-defaults
{:on-press on-press
:accounts (repeat 1 account)})])
(h/render-with-theme-provider
[saved-contact-address/view
{:on-press on-press
:accounts (repeat 1 account)}])
(h/fire-event :on-press (h/get-by-label-text :container))
(h/was-called on-press))))
@@ -1,28 +0,0 @@
(ns quo.components.list-items.saved-contact-address.schema)
(def ^:private ?contact
[:map
[:full-name :string]
[:profile-picture {:optional true} [:maybe [:or :schema.common/image-source :string]]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]])
(def ^:private ?account
[:map
[:name :string]
[:address :string]
[:emoji {:optional true} :string]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]])
(def ^:private ?accounts
[:+ ?account])
(def ?schema
[:=>
[:cat
[:map
[:contact-props ?contact]
[:accounts {:optional true} ?accounts]
[:on-press {:optional true} [:maybe fn?]]
[:active-state? {:optional true} [:maybe :boolean]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]
:any])
@@ -3,95 +3,93 @@
[quo.components.avatars.account-avatar.view :as account-avatar]
[quo.components.avatars.user-avatar.view :as user-avatar]
[quo.components.icon :as icon]
[quo.components.list-items.saved-contact-address.schema :as component-schema]
[quo.components.list-items.saved-contact-address.style :as style]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[schema.core :as schema]
[reagent.core :as reagent]
[utils.address :as address]
[utils.i18n :as i18n]))
(defn- account
[{:keys [emoji name address customization-color]}]
(let [theme (quo.theme/use-theme-value)]
[rn/view
{:accessibility-label :account-container
:style style/account-container}
[account-avatar/view
{:emoji emoji
:size 16
:customization-color customization-color}]
[text/text
{:size :paragraph-2
:weight :monospace
:style (style/account-name theme)}
name]
[rn/view {:style (style/dot-divider theme)}]
[text/text
{:size :paragraph-2
:weight :monospace
:style (style/account-address theme)}
(address/get-shortened-key address)]]))
[{:keys [emoji name address customization-color theme]}]
[rn/view
{:accessibility-label :account-container
:style style/account-container}
[account-avatar/view
{:emoji emoji
:size 16
:customization-color customization-color}]
[text/text
{:size :paragraph-2
:weight :monospace
:style (style/account-name theme)}
name]
[rn/view {:style (style/dot-divider theme)}]
[text/text
{:size :paragraph-2
:weight :monospace
:style (style/account-address theme)}
(address/get-shortened-key address)]])
(defn- internal-view
[{:keys [contact-props accounts active-state? customization-color on-press]
:or {customization-color :blue
accounts []
active-state? true}}]
(let [theme (quo.theme/use-theme-value)
[state set-state] (rn/use-state :default)
active? (rn/use-ref-atom false)
timer (rn/use-ref-atom nil)
on-press (rn/use-callback #(when on-press (on-press)))
on-press-in (rn/use-callback
(fn []
(when-not (= state :selected)
(reset! timer (js/setTimeout #(set-state :pressed) 100))))
[state])
accounts-count (count accounts)
account-props (when (= accounts-count 1) (first accounts))
on-press-out (rn/use-callback
(fn []
[]
(let [state (reagent/atom :default)
active? (atom false)
timer (atom nil)
on-press-in (fn []
(when-not (= @state :selected)
(reset! timer (js/setTimeout #(reset! state :pressed) 100))))]
(fn [{:keys [contact-props accounts active-state? customization-color on-press theme]
:or {customization-color :blue
accounts []
active-state? true}}]
(let [accounts-count (count accounts)
account-props (when (= accounts-count 1)
(first accounts))
on-press-out (fn []
(let [new-state (if (or (not active-state?) @active?) :default :active)]
(when @timer (js/clearTimeout @timer))
(reset! timer nil)
(reset! active? (= new-state :active))
(set-state new-state)))
[active-state?])]
[rn/pressable
{:style (style/container {:state state :customization-color customization-color})
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :container}
[rn/view {:style style/left-container}
[user-avatar/user-avatar (assoc contact-props :size :small)]
[rn/view {:style style/saved-contact-container}
[rn/view
{:style style/account-title-container}
[text/text
{:weight :semi-bold
:size :paragraph-1}
(:full-name contact-props)]
[icon/icon :i/contact
{:container-style style/contact-icon-container
:accessibility-label :contact-icon
:size 12
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
:color-2 colors/white}]]
(if account-props
[account (assoc account-props :theme theme)]
[text/text
{:accessibility-label :accounts-count
:size :paragraph-2
:style (style/accounts-count theme)}
(i18n/label :t/accounts-count {:count accounts-count})])]]
(when (> accounts-count 1)
[icon/icon :i/chevron-right
{:accessibility-label :check-icon
:size 20
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}])]))
(reset! state new-state)))]
[rn/pressable
{:style (style/container
{:state @state :customization-color customization-color})
:on-press-in on-press-in
:on-press-out on-press-out
:on-press #(when on-press
(on-press))
:accessibility-label :container}
[rn/view {:style style/left-container}
[user-avatar/user-avatar (assoc contact-props :size :small)]
[rn/view {:style style/saved-contact-container}
[rn/view
{:style style/account-title-container}
[text/text
{:weight :semi-bold
:size :paragraph-1}
(:full-name contact-props)]
[icon/icon :i/contact
{:container-style style/contact-icon-container
:accessibility-label :contact-icon
:size 12
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
:color-2 colors/white}]]
(if account-props
[account (assoc account-props :theme theme)]
[text/text
{:accessibility-label :accounts-count
:size :paragraph-2
:style (style/accounts-count theme)}
(i18n/label :t/accounts-count {:count accounts-count})])]]
(when (> accounts-count 1)
[icon/icon :i/chevron-right
{:accessibility-label :check-icon
:size 20
:color (colors/theme-colors colors/neutral-50
colors/neutral-40
theme)}])]))))
(def view (schema/instrument #'internal-view component-schema/?schema))
(def view (quo.theme/with-theme internal-view))
@@ -8,36 +8,40 @@
{:token :snt
:state :default
:label "Status"
:networks [{:source (resources/get-network :ethereum)}]
:networks [(resources/get-network :ethereum)]
:token-value "100.00 SNT"
:fiat-value "€0.00"})
(h/describe "List items: Token network"
(h/test "default state"
(h/render-with-theme-provider [token-network/view (dissoc props :state)])
(h/render [token-network/view (dissoc props :state)])
(h/is-truthy (h/get-by-text "Status")))
(h/test "default state explicit"
(h/render-with-theme-provider [token-network/view props])
(h/render [token-network/view props])
(h/is-truthy (h/get-by-text "Status")))
(h/test "Pressed state"
(h/render-with-theme-provider [token-network/view props])
(h/render [token-network/view props])
(h/fire-event :on-press-in (h/get-by-label-text :token-network))
(h/wait-for #(h/has-style (h/query-by-label-text :token-network)
{:backgroundColor (colors/resolve-color :blue :light 5)})))
(h/test "Active state"
(h/render-with-theme-provider [token-network/view (assoc props :state :active)])
(h/render [token-network/view (assoc props :state :active)])
(h/has-style (h/query-by-label-text :token-network)
{:backgroundColor (colors/resolve-color :blue :light 10)}))
(h/test "Selected state"
(h/render-with-theme-provider [token-network/view (assoc props :state :selected)])
(h/render [token-network/view (assoc props :state :selected)])
(h/is-truthy (h/query-by-label-text :check-icon)))
(h/test "Call on-press"
(let [on-press (h/mock-fn)]
(h/render-with-theme-provider [token-network/view (assoc props :on-press on-press)])
(h/render [token-network/view (assoc props :on-press on-press)])
(h/fire-event :on-press (h/get-by-label-text :token-network))
(h/was-called on-press))))
(h/was-called on-press)))
(h/test "Empty props"
(h/render [token-network/view {}])
(h/is-truthy (h/get-by-label-text :token-network))))
@@ -1,15 +0,0 @@
(ns quo.components.list-items.token-network.schema)
(def ?schema
[:=>
[:cat
[:map
[:token :keyword]
[:label :string]
[:token-value :string]
[:fiat-value :string]
[:networks [:* [:map [:source :schema.common/image-source]]]]
[:on-press {:optional true} [:maybe fn?]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:state {:optional true} [:enum :default :active :selected]]]]
:any])
@@ -2,13 +2,12 @@
(:require
[quo.components.icon :as icon]
[quo.components.list-items.preview-list.view :as preview-list]
[quo.components.list-items.token-network.schema :as component-schema]
[quo.components.list-items.token-network.style :as style]
[quo.components.markdown.text :as text]
[quo.components.utilities.token.view :as token]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
[reagent.core :as reagent]))
(defn- info
[{:keys [token label networks]}]
@@ -30,40 +29,42 @@
networks]]])
(defn- values
[{:keys [state token-value fiat-value customization-color]}]
(let [theme (quo.theme/use-theme-value)]
(if (= state :selected)
[icon/icon :i/check
{:color (style/check-color customization-color theme)
:accessibility-label :check-icon}]
[rn/view {:style style/values-container}
[text/text
{:weight :medium
:size :paragraph-2
:number-of-lines 1}
token-value]
[text/text
{:style (style/fiat-value theme)
:size :paragraph-2
:number-of-lines 1}
fiat-value]])))
[{:keys [state token-value fiat-value customization-color theme]}]
(if (= state :selected)
[icon/icon :i/check
{:color (style/check-color customization-color theme)
:accessibility-label :check-icon}]
[rn/view {:style style/values-container}
[text/text
{:weight :medium
:size :paragraph-2
:number-of-lines 1}
token-value]
[text/text
{:style (style/fiat-value theme)
:size :paragraph-2
:number-of-lines 1}
fiat-value]]))
(defn- view-internal
[{:keys [on-press state customization-color _token _networks _token-value _fiat-value]
:as props
:or {customization-color :blue}}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))
internal-state (if pressed? :pressed state)]
[rn/pressable
{:style (style/container internal-state customization-color theme)
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :token-network}
[info props]
[values props]]))
[]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)]
(fn [{:keys [on-press state customization-color
_token _networks _token-value _fiat-value theme]
:as props
:or {customization-color :blue}}]
(let [internal-state (if @pressed?
:pressed
state)]
[rn/pressable
{:style (style/container internal-state customization-color theme)
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:accessibility-label :token-network}
[info props]
[values props]]))))
(def view (schema/instrument #'view-internal component-schema/?schema))
(def view (quo.theme/with-theme view-internal))
@@ -5,29 +5,29 @@
(h/describe "List Items: Token Value"
(h/test "Token label renders"
(h/render-with-theme-provider [token-value/view
{:token :snt
:token-name "Status"
:state :default
:status :empty
:customization-color :blue
:metrics? true
:values {:crypto-value "0.00"
:fiat-value "€0.00"
:percentage-change "0.00"
:fiat-change "€0.00"}}])
(h/render [token-value/view
{:token :snt
:token-name "Status"
:state :default
:status :empty
:customization-color :blue
:metrics? true
:values {:crypto-value "0.00"
:fiat-value "€0.00"
:percentage-change "0.00"
:fiat-change "€0.00"}}])
(h/is-truthy (h/get-by-text "Status")))
(h/test "Status change"
(h/render-with-theme-provider [token-value/view
{:token :snt
:token-name "Status"
:state :default
:status :positive
:customization-color :blue
:metrics? true
:values {:crypto-value "0.00"
:fiat-value "€0.00"
:percentage-change "0.00"
:fiat-change "€0.00"}}])
(h/render [token-value/view
{:token :snt
:token-name "Status"
:state :default
:status :positive
:customization-color :blue
:metrics? true
:values {:crypto-value "0.00"
:fiat-value "€0.00"
:percentage-change "0.00"
:fiat-change "€0.00"}}])
(h/is-truthy (h/get-by-label-text :arrow-icon))))
@@ -1,22 +0,0 @@
(ns quo.components.list-items.token-value.schema)
(def ^:private ?values
[:map
[:crypto-value :string]
[:fiat-value :string]
[:percentage-change {:optional true} :string]
[:fiat-change {:optional true} :string]])
(def ?schema
[:=>
[:cat
[:map
[:token [:or keyword? string?]]
[:token-name :string]
[:status [:enum :empty :positive :negative]]
[:values ?values]
[:on-press {:optional true} [:maybe fn?]]
[:on-long-press {:optional true} [:maybe fn?]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:metrics? {:optional true} [:maybe :boolean]]]]
:any])
@@ -2,74 +2,67 @@
(:require
[clojure.string :as string]
[quo.components.icon :as icon]
[quo.components.list-items.token-value.schema :as component-schema]
[quo.components.list-items.token-value.style :as style]
[quo.components.markdown.text :as text]
[quo.components.utilities.token.view :as token]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
[reagent.core :as reagent]))
(defn- internal-view
[{:keys [customization-color status token metrics? values on-press on-long-press token-name]}]
(let [theme (quo.theme/use-theme-value)
[state set-state] (rn/use-state :default)
bg-opacity (case state
:active 10
:pressed 5
0)
{:keys [crypto-value
fiat-value
percentage-change
fiat-change]} values
on-press-in (rn/use-callback #(set-state :pressed))
on-press-out (rn/use-callback #(set-state :default))
on-press (rn/use-callback
(fn []
(set-state :active)
(js/setTimeout #(set-state :default) 300)
on-press))]
[rn/pressable
{:style (style/container customization-color bg-opacity theme)
:on-press-in on-press-in
:on-press-out on-press-out
:on-press on-press
:on-long-press on-long-press
:accessibility-label :container}
[rn/view
{:style {:flex-direction :row
:align-items :center
:flex 1}}
[token/view {:token token :size :size-32}]
[rn/view {:style {:margin-left 8}}
[text/text {:weight :semi-bold} token-name]
[text/text
{:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
(str crypto-value " " (if token (string/upper-case (clj->js token)) ""))]]]
[rn/view
{:style {:align-items :flex-end
:justify-content :space-between}}
[text/text
{:weight :medium
:size :paragraph-2} fiat-value]
(when metrics?
[rn/view
{:style {:flex-direction :row
:align-items :center}}
[text/text
{:size :paragraph-2
:style (style/metric-text status theme)} (str percentage-change "%")]
[rn/view {:style (style/dot-divider status theme)}]
[text/text
{:size :paragraph-2
:style (style/metric-text status theme)} fiat-change]
(when (not= status :empty)
[rn/view
{:style {:margin-left 4}
:accessibility-label :arrow-icon}
[icon/icon (if (= status :positive) :i/positive :i/negative)
(style/arrow-icon status theme)]])])]]))
[]
(let [state (reagent/atom :default)]
(fn [{:keys [theme customization-color status token metrics? values on-press on-long-press
token-name]}]
(let [bg-opacity (case @state
:active 10
:pressed 5
0)
{:keys [crypto-value fiat-value percentage-change fiat-change]} values]
[rn/pressable
{:style (style/container customization-color bg-opacity theme)
:on-press-in #(reset! state :pressed)
:on-press-out #(reset! state :default)
:on-press (fn []
(reset! state :active)
(js/setTimeout #(reset! state :default) 300)
on-press)
:on-long-press on-long-press
:accessibility-label :container}
[rn/view
{:style {:flex-direction :row
:align-items :center
:flex 1}}
[token/view {:token token :size :size-32}]
[rn/view {:style {:margin-left 8}}
[text/text {:weight :semi-bold} token-name]
[text/text
{:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
(str crypto-value " " (if token (string/upper-case (clj->js token)) ""))]]]
[rn/view
{:style {:align-items :flex-end
:justify-content :space-between}}
[text/text
{:weight :medium
:size :paragraph-2} fiat-value]
(when metrics?
[rn/view
{:style {:flex-direction :row
:align-items :center}}
[text/text
{:size :paragraph-2
:style (style/metric-text status theme)} (str percentage-change "%")]
[rn/view {:style (style/dot-divider status theme)}]
[text/text
{:size :paragraph-2
:style (style/metric-text status theme)} fiat-change]
(when (not= status :empty)
[rn/view
{:style {:margin-left 4}
:accessibility-label :arrow-icon}
[icon/icon (if (= status :positive) :i/positive :i/negative)
(style/arrow-icon status theme)]])])]]))))
(def view (schema/instrument #'internal-view component-schema/?schema))
(def view (quo.theme/with-theme internal-view))
@@ -35,7 +35,7 @@
:on-press on-press
:background (if behind-overlay?
:blur
(when (button-properties/backgrounds background) background))
(button-properties/backgrounds background))
:accessibility-label accessibility-label}
icon-name])]
children))
@@ -1,9 +1,9 @@
(ns quo.components.profile.collectible.view
(:require
[clojure.string :as string]
[quo.components.markdown.text :as text]
[quo.components.profile.collectible.style :as style]
[react-native.core :as rn]))
[react-native.core :as rn]
[react-native.svg :as svg]))
(defn remaining-tiles
[amount]
@@ -19,28 +19,11 @@
(let [svg? (and (map? resource) (:svg? resource))
image-style (style/tile-style-by-size size)]
[rn/view {:style style}
(cond
svg?
(if svg?
[rn/view
{:style (assoc image-style
:border-radius (:border-radius image-style)
:overflow :hidden
:justify-content :center
:align-items :center
:background-color :lightblue)}
[text/text "SVG Content"]]
(or (string/blank? resource) (string/blank? (:uri resource)))
[rn/view
{:style (assoc image-style
:border-radius (:border-radius image-style)
:overflow :hidden
:justify-content :center
:align-items :center
:background-color :lightgray)}
[text/text "Missing image"]]
:else
{:style {:border-radius (:border-radius image-style)
:overflow :hidden}}
[svg/svg-uri (assoc image-style :uri (:uri resource))]]
;; NOTE: using react-native-fast-image here causes a crash on devices when used inside a
;; large flatlist. The library seems to have issues with memory consumption when used with
;; large images/GIFs.
@@ -4,10 +4,10 @@
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.core :refer [use-effect]]
[react-native.reanimated :as reanimated]))
(defn delete-button
(defn f-delete-button
[recording? ready-to-delete? reviewing-audio? force-show-controls?]
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-x (reanimated/use-shared-value (if force-show-controls? 35 20))
@@ -17,68 +17,62 @@
connector-height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-animation (rn/use-callback
(fn []
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80)))
reset-x-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))
fade-in-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x 0 200)
(helpers/animate-linear opacity 1 200)))
fade-out-animation (rn/use-callback
(fn []
(helpers/animate-linear
translate-x
(if reviewing-audio? 35 20)
200)
(if reviewing-audio?
(helpers/animate-linear scale 0.75 200)
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
[reviewing-audio?])
fade-out-reset-animation (rn/use-callback
(fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-x 20 0 200)
(helpers/animate-linear-with-delay scale 1 0 200)))]
(rn/use-effect (fn []
(if recording?
(fade-in-animation)
(fade-out-animation)))
[recording?])
(rn/use-effect (fn []
(when-not reviewing-audio?
(fade-out-reset-animation)))
[reviewing-audio?])
(rn/use-effect (fn []
(cond
ready-to-delete?
(start-x-animation)
recording?
(reset-x-animation)))
[ready-to-delete?])
start-x-animation (fn []
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-x-animation (fn []
(helpers/animate-linear translate-x 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x 0 200)
(helpers/animate-linear opacity 1 200))
fade-out-animation (fn []
(helpers/animate-linear
translate-x
(if @reviewing-audio? 35 20)
200)
(if @reviewing-audio?
(helpers/animate-linear scale 0.75 200)
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-x 20 0 200)
(helpers/animate-linear-with-delay scale 1 0 200))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-delete?
(start-x-animation)
@recording?
(reset-x-animation)))
[@ready-to-delete?])
[:<>
[reanimated/view {:style (style/delete-button-container opacity)}
[reanimated/view
@@ -5,10 +5,10 @@
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.core :refer [use-effect]]
[react-native.reanimated :as reanimated]))
(defn lock-button
(defn f-lock-button
[recording? ready-to-lock? locked?]
(let [theme (quo.theme/use-theme-value)
translate-x-y (reanimated/use-shared-value 20)
@@ -18,58 +18,54 @@
height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-y-animation (rn/use-callback
(fn []
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
(helpers/animate-easing-with-delay width 56 83.33 63.33)
(helpers/animate-easing-with-delay height 56 83.33 63.33)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
63.33)))
reset-x-y-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))
fade-in-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x-y 0 220)
(helpers/animate-linear opacity 1 220)))
fade-out-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x-y 20 200)
(helpers/animate-linear opacity 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))]
(rn/use-effect (fn []
(if recording?
(fade-in-animation)
(fade-out-animation)))
[recording?])
(rn/use-effect (fn []
(cond
ready-to-lock?
(start-x-y-animation)
(and recording? (not locked?))
(reset-x-y-animation)))
[ready-to-lock?])
(rn/use-effect (fn []
(if locked?
(fade-out-animation)
(reset-x-y-animation)))
[locked?])
start-x-y-animation (fn []
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
(helpers/animate-easing-with-delay width 56 83.33 63.33)
(helpers/animate-easing-with-delay height 56 83.33 63.33)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
63.33))
reset-x-y-animation (fn []
(helpers/animate-linear translate-x-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x-y 0 220)
(helpers/animate-linear opacity 1 220))
fade-out-animation (fn []
(helpers/animate-linear translate-x-y 20 200)
(helpers/animate-linear opacity 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(cond
@ready-to-lock?
(start-x-y-animation)
(and @recording? (not @locked?))
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @locked?
(fade-out-animation)
(reset-x-y-animation)))
[@locked?])
[:<>
[reanimated/view {:style (style/lock-button-container opacity)}
[reanimated/view
@@ -81,6 +77,6 @@
[reanimated/view
{:style (style/lock-button translate-x-y opacity)
:pointer-events :none}
[icons/icon (if ready-to-lock? :i/locked :i/unlocked)
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
{:color (colors/theme-colors colors/black colors/white theme)
:size 20}]]]))
@@ -3,17 +3,19 @@
[quo.components.buttons.button.view :as button]
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[react-native.core :as rn]
[react-native.core :as rn :refer [use-effect]]
[react-native.reanimated :as reanimated]))
(defn record-button
(defn f-record-button
[recording? reviewing-audio?]
(let [opacity (reanimated/use-shared-value 1)]
(rn/use-effect (fn []
(if (or recording? reviewing-audio?)
(helpers/set-value opacity 0)
(helpers/set-value opacity 1)))
[recording? reviewing-audio?])
(let [opacity (reanimated/use-shared-value 1)
show-animation #(helpers/set-value opacity 1)
hide-animation #(helpers/set-value opacity 0)]
(use-effect (fn []
(if (or @recording? @reviewing-audio?)
(hide-animation)
(show-animation)))
[@recording? @reviewing-audio?])
[reanimated/view {:style (style/record-button-container opacity)}
[button/button
{:type :outline
@@ -5,9 +5,8 @@
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.audio-toolkit :as audio]
[react-native.core :as rn]
[react-native.core :as rn :refer [use-effect]]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[taoensso.timbre :as log]
@@ -29,16 +28,13 @@
(reagent/as-element
[reanimated/view {:style (style/animated-circle scale opacity color)}]))))))
(defn record-button-big
[{:keys [recording? set-recording ready-to-send? set-ready-to-send ready-to-lock? set-ready-to-lock
ready-to-delete? set-ready-to-delete record-button-is-animating?
record-button-at-initial-position? locked? set-locked reviewing-audio? recording-length-ms
set-recording-length-ms
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]}]
(let [theme (quo.theme/use-theme-value)
scale (reanimated/use-shared-value 1)
(defn f-record-button-big
[{:keys [recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating?
record-button-at-initial-position? locked? reviewing-audio? recording-length-ms
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel theme]}]
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if ready-to-lock? opacity-from-lock opacity-from-default)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (worklets.record-audio/ring-scale scale
@@ -50,155 +46,141 @@
[opacity-from 0])}))
(range 0 5))
rings-color (cond
ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80
theme)
ready-to-delete? colors/danger-50
:else colors/primary-50)
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80
theme)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
translate-y (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 0)
button-color colors/primary-50
icon-color (if (and (not (= :dark theme)) ready-to-lock?) colors/black colors/white)
icon-color (if (and (not (= :dark theme)) @ready-to-lock?) colors/black colors/white)
icon-opacity (reanimated/use-shared-value 1)
red-overlay-opacity (reanimated/use-shared-value 0)
gray-overlay-opacity (reanimated/use-shared-value 0)
complete-animation
(rn/use-callback
(fn []
(cond
(and ready-to-lock? (not @record-button-is-animating?))
(do
(set-locked true)
(set-ready-to-lock false))
(and (not locked?) (not reviewing-audio?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration recording-length-ms}))
ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder-fn)
(set-recording false)
(set-ready-to-send false)
(set-ready-to-delete false)
(set-ready-to-lock false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
[ready-to-lock? locked? reviewing-audio? ready-to-send? recording-length-ms ready-to-delete?
idle?])
start-animation (rn/use-callback
(fn []
(helpers/set-value opacity 1)
(helpers/animate-linear scale 2.6 signal-anim-duration)
;; TODO: Research if we can implement this with withSequence method
;; from Reanimated 2
;; GitHub issue [#14561]:
;; https://github.com/status-im/status-mobile/issues/14561
(reset! clear-timeout
(js/setTimeout
(fn []
(helpers/set-value scale scale-to-each)
(helpers/animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration))))
stop-animation (rn/use-callback
(fn []
(helpers/set-value opacity 0)
(reanimated/cancel-animation scale)
(helpers/set-value scale 1)
(when @clear-timeout (js/clearTimeout @clear-timeout))))
start-y-animation (rn/use-callback
(fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
[complete-animation])
reset-y-animation (rn/use-callback
(fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-linear icon-opacity 1 500)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500)))
start-x-animation (rn/use-callback
(fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-x -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(helpers/animate-linear red-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
[complete-animation])
reset-x-animation (rn/use-callback
(fn []
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear red-overlay-opacity 0 100)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500)))
start-x-y-animation (rn/use-callback
(fn []
(cond
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @locked?) (not @reviewing-audio?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration @recording-length-ms}))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder-fn)
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
start-animation (fn []
(helpers/set-value opacity 1)
(helpers/animate-linear scale 2.6 signal-anim-duration)
;; TODO: Research if we can implement this with withSequence method
;; from Reanimated 2
;; GitHub issue [#14561]:
;; https://github.com/status-im/status-mobile/issues/14561
(reset! clear-timeout
(js/setTimeout
(fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -44 200)
(helpers/animate-easing translate-x -44 200)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
(helpers/animate-linear gray-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
[complete-animation])
reset-x-y-animation (rn/use-callback
(fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800)))]
(rn/use-effect (fn []
(cond
recording?
(start-animation)
(not ready-to-lock?)
(stop-animation)))
[recording?])
(rn/use-effect (fn []
(if ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation)))
[ready-to-lock?])
(rn/use-effect (fn []
(if ready-to-send?
(start-y-animation)
(reset-y-animation)))
[ready-to-send?])
(rn/use-effect (fn []
(if ready-to-delete?
(start-x-animation)
(reset-x-animation)))
[ready-to-delete?])
(helpers/set-value scale scale-to-each)
(helpers/animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration)))
stop-animation (fn []
(helpers/set-value opacity 0)
(reanimated/cancel-animation scale)
(helpers/set-value scale 1)
(when @clear-timeout (js/clearTimeout @clear-timeout)))
start-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-linear icon-opacity 1 500)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-x -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(helpers/animate-linear red-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-x-animation (fn []
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear red-overlay-opacity 0 100)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -44 200)
(helpers/animate-easing translate-x -44 200)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
(helpers/animate-linear gray-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
reset-x-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800))]
(use-effect (fn []
(cond
@recording?
(start-animation)
(not @ready-to-lock?)
(stop-animation)))
[@recording?])
(use-effect (fn []
(if @ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @ready-to-send?
(start-y-animation)
(reset-y-animation)))
[@ready-to-send?])
(use-effect (fn []
(if @ready-to-delete?
(start-x-animation)
(reset-x-animation)))
[@ready-to-delete?])
[reanimated/view
{:style (style/record-button-big-container translate-x translate-y opacity)
:pointer-events :none}
@@ -215,6 +197,6 @@
[reanimated/view {:style (style/record-button-big-red-overlay red-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-gray-overlay gray-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-icon-container icon-opacity)}
(if locked?
(if @locked?
[rn/view {:style style/stop-icon}]
[icons/icon :i/audio {:color icon-color}])]]]))
@@ -4,10 +4,10 @@
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.core :refer [use-effect]]
[react-native.reanimated :as reanimated]))
(defn send-button
(defn f-send-button
[recording? ready-to-send? reviewing-audio? force-show-controls?]
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-y (reanimated/use-shared-value (if force-show-controls? 76 20))
@@ -16,71 +16,65 @@
height (reanimated/use-shared-value 24)
border-radius-first-half (reanimated/use-shared-value 16)
border-radius-second-half (reanimated/use-shared-value 8)
start-y-animation (rn/use-callback
(fn []
(helpers/animate-linear-with-delay translate-y 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay width 56 83.33 80)
(helpers/animate-easing-with-delay height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80)))
reset-y-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 12)
(helpers/set-value height 24)
(helpers/set-value border-radius-first-half 16)
(helpers/set-value border-radius-second-half 8)))
fade-in-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-y 0 200)
(helpers/animate-linear opacity 1 200)))
fade-out-animation (rn/use-callback
(fn []
(when-not force-show-controls?
(helpers/animate-linear
translate-y
(if reviewing-audio? 76 20)
200))
(when-not reviewing-audio?
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
[reviewing-audio? force-show-controls?])
fade-out-reset-animation (rn/use-callback
(fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-y 20 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))]
(rn/use-effect (fn []
(if recording?
(fade-in-animation)
(fade-out-animation)))
[recording?])
(rn/use-effect (fn []
(when-not reviewing-audio?
(fade-out-reset-animation)))
[reviewing-audio?])
(rn/use-effect (fn []
(cond
ready-to-send?
(start-y-animation)
recording? (reset-y-animation)))
[ready-to-send?])
start-y-animation (fn []
(helpers/animate-linear-with-delay translate-y 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay width 56 83.33 80)
(helpers/animate-easing-with-delay height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-y-animation (fn []
(helpers/animate-linear translate-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 12)
(helpers/set-value height 24)
(helpers/set-value border-radius-first-half 16)
(helpers/set-value border-radius-second-half 8))
fade-in-animation (fn []
(helpers/animate-linear translate-y 0 200)
(helpers/animate-linear opacity 1 200))
fade-out-animation (fn []
(when-not force-show-controls?
(helpers/animate-linear
translate-y
(if @reviewing-audio? 76 20)
200))
(when-not @reviewing-audio?
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-y 20 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-send?
(start-y-animation)
@recording? (reset-y-animation)))
[@ready-to-send?])
[:<>
[reanimated/view {:style (style/send-button-container opacity)}
[reanimated/view
@@ -2,6 +2,7 @@
(:require
[quo.components.record-audio.record-audio.view :as record-audio]
[react-native.audio-toolkit :as audio]
[reagent.core :as reagent]
[test-helpers.component :as h]
[utils.datetime :as datetime]))
@@ -10,10 +11,8 @@
(h/test "renders record-audio"
(h/render [record-audio/record-audio])
(-> (h/wait-for #(h/get-by-test-id "record-audio"))
(.then (fn []
(-> (h/expect (h/get-by-test-id "record-audio"))
(.toBeTruthy))))))
(-> (h/expect (h/get-by-test-id "record-audio"))
(.toBeTruthy)))
(h/test "record-audio on-start-recording works"
(let [event (js/jest.fn)]
@@ -32,7 +31,7 @@
(h/test "record-audio on-reviewing-audio works"
(let [event (js/jest.fn)
on-meter (atom nil)]
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-reviewing-audio event
:record-audio-permission-granted true}])
@@ -40,7 +39,7 @@
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval @on-meter 100))
(js/setInterval #(@on-meter) 100))
audio/get-recorder-file-path (fn [] "file-path")]
(h/fire-event
:on-start-should-set-responder
@@ -49,7 +48,6 @@
:locationY 70
:timestamp 0
:identifier 0}})
(with-redefs [datetime/timestamp (fn [] (+ (.now js/Date) 1000))]
(h/advance-timers-by-time 100)
(h/fire-event
@@ -65,7 +63,7 @@
(h/test "record-audio on-send works after reviewing audio"
(let [event (js/jest.fn)
on-meter (atom nil)]
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-send event
:record-audio-permission-granted true}])
@@ -116,15 +114,14 @@
(h/test "record-audio on-send works after sliding to the send button"
(let [event (js/jest.fn)
on-meter (atom nil)
on-meter (reagent/atom nil)
last-now-ms (atom nil)
duration-ms (atom nil)]
(h/render [record-audio/record-audio
{:on-send event
:record-audio-permission-granted true}])
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn)
#js {:destroy #()})
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))
@@ -173,16 +170,15 @@
(h/test "record-audio on-cancel works after reviewing audio"
(let [event (js/jest.fn)
on-meter (atom nil)]
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-cancel event
:record-audio-permission-granted true}])
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn)
#js {:destroy #()})
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval @on-meter 100))]
(js/setInterval #(@on-meter) 100))]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
@@ -199,7 +195,6 @@
:locationY 70
:timestamp 200
:identifier 0}})
(h/advance-timers-by-time 250)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
@@ -207,12 +202,13 @@
:locationY 80
:timestamp 200
:identifier 0}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))))))
(h/test "record-audio on-cancel works after sliding to the cancel button"
(let [event (js/jest.fn)
on-meter (atom nil)]
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-cancel event
:record-audio-permission-granted true}])
@@ -1,81 +0,0 @@
(ns quo.components.record-audio.record-audio.constants
(:require [react-native.audio-toolkit :as audio]
[react-native.platform :as platform]
[utils.datetime :as datetime]))
(def min-audio-duration-ms 1000)
(def max-audio-duration-ms (if platform/ios? 120800 120500))
(def metering-interval 25)
(def base-filename "am")
(def default-format ".aac")
(def min-touch-duration 150)
(def record-button-area-big
{:width 56
:height 56
:x 64
:y 64})
(def record-button-area
{:width 48
:height 48
:x 68
:y 68})
(defn delete-button-area
[{:keys [active? reviewing-audio?]}]
{:width (cond
active? 72
reviewing-audio? 32
:else 82)
:height (if reviewing-audio? 32 56)
:x (cond
active? -16
reviewing-audio? 36
:else -32)
:y (cond
active? 64
reviewing-audio? 76
:else 70)})
(defn lock-button-area
[{:keys [active?]}]
{:width (if active? 72 100)
:height (if active? 72 102)
:x -32
:y -32})
(defn send-button-area
[{:keys [active? reviewing-audio?]}]
{:width (if reviewing-audio? 32 56)
:height (cond
active? 72
reviewing-audio? 47
:else 92)
:x (if reviewing-audio? 76 32)
:y (cond
active? -16
reviewing-audio? 76
:else -32)})
(defn touch-inside-area?
[{:keys [location-x location-y ignore-min-y? ignore-max-y? ignore-min-x? ignore-max-x?]}
{:keys [width height x y]}]
(let [max-x (+ x width)
max-y (+ y height)]
(and
(and
(or ignore-min-x? (>= location-x x))
(or ignore-max-x? (<= location-x max-x)))
(and
(or ignore-min-y? (>= location-y y))
(or ignore-max-y? (<= location-y max-y))))))
(def rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename
(datetime/timestamp)
default-format)
:meteringInterval metering-interval}))
@@ -1,345 +0,0 @@
(ns quo.components.record-audio.record-audio.handlers
(:require [oops.core :as oops]
[quo.components.record-audio.record-audio.constants :as record-audio.constants]
[react-native.audio-toolkit :as audio]
[taoensso.timbre :as log]
[utils.datetime :as datetime]))
(def get-reload-player
(memoize
(fn
[[player-ref destroy-player set-playing-audio playing-timer set-audio-current-time-ms
set-seeking-audio]]
(fn [audio-file]
(when @player-ref
(destroy-player))
(reset! player-ref
(audio/new-player
(or audio-file (:filename record-audio.constants/rec-options))
{:autoDestroy false
:continuesToPlayInBackground false
:category audio/PLAYBACK}
(fn []
(set-playing-audio false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil)
(set-audio-current-time-ms 0)
(set-seeking-audio false)))))
(audio/prepare-player
@player-ref
#(log/debug "[record-audio] prepare player - success")
#(log/error "[record-audio] prepare player - error: " %))))))
(def get-recorder-on-meter
(memoize
(fn
[[recording-start-ms set-recording-length-ms reached-max-duration? locked? set-locked
set-reviewing-audio
idle? set-recording
set-ready-to-lock set-ready-to-send set-ready-to-delete recorder-ref output-file reload-player
on-reviewing-audio]]
(fn []
(when @recording-start-ms
(let [now-ms (datetime/timestamp)
recording-duration (- now-ms @recording-start-ms)]
(set-recording-length-ms recording-duration)
(when (>= recording-duration record-audio.constants/max-audio-duration-ms)
(reset! reached-max-duration? (not locked?))
(set-reviewing-audio true)
(reset! idle? false)
(set-locked false)
(set-recording false)
(set-ready-to-lock false)
(set-ready-to-send false)
(set-ready-to-delete false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
(reload-player nil)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(reset! recording-start-ms nil)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
(log/debug "[record-audio] new recorder - on meter")))))))
(def get-reload-recorder
(memoize
(fn
[[recorder-ref recorder-on-meter]]
(fn []
(when @recorder-ref
(audio/destroy-recorder @recorder-ref)
(reset! recorder-ref nil))
(reset! recorder-ref (audio/new-recorder
record-audio.constants/rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended")))))))
(def get-on-start-should-set-responder
(memoize
(fn
[[locked? idle? disabled? recorder-on-meter touch-timestamp touch-identifier
reviewing-audio? record-audio-permission-granted set-recording set-playing-audio output-file
recorder-ref recording-start-ms set-audio-current-time-ms on-start-recording
on-request-record-audio-permission touch-active?]]
(fn [e]
(when-not (or locked? @idle? (nil? e) @disabled?)
(let [pressed-record-button? (record-audio.constants/touch-inside-area?
{:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY")
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
record-audio.constants/record-button-area)
new-recorder (audio/new-recorder
record-audio.constants/rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))]
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
(reset! touch-identifier (oops/oget e "nativeEvent.identifier"))
(when-not reviewing-audio?
(if record-audio-permission-granted
(do
(when (not @idle?)
(set-recording pressed-record-button?))
(when pressed-record-button?
(set-playing-audio false)
(reset! output-file nil)
(reset! recorder-ref new-recorder)
(audio/start-recording
new-recorder
(fn []
(reset! recording-start-ms (datetime/timestamp))
(set-audio-current-time-ms 0)
(log/debug "[record-audio] start recording - success"))
#(log/error "[record-audio] start recording - error: " %))
(when on-start-recording
(on-start-recording))))
(when on-request-record-audio-permission
(on-request-record-audio-permission))))
(when record-audio-permission-granted
(reset! touch-active? true))))
(and (not @idle?) (not @disabled?))))))
(def get-on-responder-move
(memoize
(fn
[[locked? ready-to-send? set-ready-to-send ready-to-delete? set-ready-to-delete ready-to-lock?
set-ready-to-lock touch-identifier
record-button-at-initial-position? recording?]]
(fn [^js e]
(when-not locked?
(let [location-x (oops/oget e "nativeEvent.locationX")
location-y (oops/oget e "nativeEvent.locationY")
page-x (oops/oget e "nativeEvent.pageX")
page-y (oops/oget e "nativeEvent.pageY")
identifier (oops/oget e "nativeEvent.identifier")
moved-to-send-button? (record-audio.constants/touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? true
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? true}
(record-audio.constants/send-button-area
{:active? ready-to-send?
:reviewing-audio? false}))
moved-to-delete-button? (record-audio.constants/touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? true
:ignore-min-x? true
:ignore-max-x? false}
(record-audio.constants/delete-button-area
{:active? ready-to-delete?
:reviewing-audio? false}))
moved-to-lock-button? (record-audio.constants/touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
(record-audio.constants/lock-button-area {:active?
ready-to-lock?}))
moved-to-record-button? (and
(record-audio.constants/touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
record-audio.constants/record-button-area-big)
(not= location-x page-x)
(not= location-y page-y))]
(when (= identifier @touch-identifier)
(cond
(and
(or
(and moved-to-record-button? ready-to-lock?)
(and (not locked?) moved-to-lock-button? @record-button-at-initial-position?))
(not ready-to-delete?)
(not ready-to-send?)
recording?)
(set-ready-to-lock moved-to-lock-button?)
(and
(or
(and moved-to-record-button? ready-to-delete?)
(and moved-to-delete-button? @record-button-at-initial-position?))
(not ready-to-lock?)
(not ready-to-send?)
recording?)
(set-ready-to-delete moved-to-delete-button?)
(and
(or
(and moved-to-record-button? ready-to-send?)
(and moved-to-send-button? @record-button-at-initial-position?))
(not ready-to-lock?)
(not ready-to-delete?)
recording?)
(set-ready-to-send moved-to-send-button?)))))))))
(def get-on-responder-release
(memoize
(fn
[[idle? reached-max-duration? touch-timestamp recording-length-ms set-recording-length-ms
reviewing-audio? set-reviewing-audio
set-audio-current-time-ms set-force-show-controls on-send output-file player-ref
destroy-player on-cancel record-button-is-animating? ready-to-lock? set-ready-to-lock
locked? set-locked on-lock ready-to-delete? set-ready-to-delete ready-to-send? set-ready-to-send
disabled? on-reviewing-audio recorder-ref set-recording reload-player recording-start-ms
touch-active?]]
(fn [^js e]
(when (and
(not @idle?)
(not @reached-max-duration?))
(let [touch-area {:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY")
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
on-record-button? (record-audio.constants/touch-inside-area?
touch-area
record-audio.constants/record-button-area-big)
on-send-button? (record-audio.constants/touch-inside-area?
touch-area
(record-audio.constants/send-button-area
{:active? false
:reviewing-audio? true}))
on-delete-button? (record-audio.constants/touch-inside-area?
touch-area
(record-audio.constants/delete-button-area
{:active? false
:reviewing-audio? true}))
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
audio-length recording-length-ms]
(cond
(and reviewing-audio? on-send-button?)
(do
(set-reviewing-audio false)
(set-audio-current-time-ms 0)
(set-force-show-controls false)
(when on-send
(on-send {:file-path @output-file
:duration (min record-audio.constants/max-audio-duration-ms
(int (audio/get-player-duration @player-ref)))}))
(when @player-ref
(audio/stop-playing
@player-ref
(fn []
(destroy-player)
(log/debug "[record-audio] stop playing - success"))
#(log/error "[record-audio] stop playing - error: " %))))
(and reviewing-audio? on-delete-button?)
(do
(set-reviewing-audio false)
(set-audio-current-time-ms 0)
(set-force-show-controls false)
(destroy-player)
(when on-cancel
(on-cancel)))
(and ready-to-lock? (not @record-button-is-animating?))
(do
(set-locked true)
(set-ready-to-lock false)
(when on-lock
(on-lock)))
(and (not reviewing-audio?)
(or on-record-button?
(and (not ready-to-delete?)
(not ready-to-lock?)
(not ready-to-send?))))
(do
(reset! disabled? (<= touch-timestamp-diff record-audio.constants/min-touch-duration))
(js/setTimeout
(fn []
(if (>= recording-length-ms record-audio.constants/min-audio-duration-ms)
(do (set-reviewing-audio true)
(reset! idle? false)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
(do (when on-cancel
(on-cancel))
(reset! idle? true)))
(set-locked false)
(set-recording false)
(set-ready-to-lock false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
(when (>= audio-length record-audio.constants/min-audio-duration-ms)
(reload-player nil))
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false))
(if (> touch-timestamp-diff record-audio.constants/min-touch-duration) 0 250)))
(and (not locked?) (not reviewing-audio?) (not @record-button-is-animating?))
(do
(reset! disabled? (<= touch-timestamp-diff record-audio.constants/min-touch-duration))
(js/setTimeout
(fn []
(audio/stop-recording
@recorder-ref
(fn []
(cond
ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration recording-length-ms}))
ready-to-delete?
(when on-cancel
(on-cancel)))
(set-recording false)
(set-ready-to-send false)
(set-ready-to-delete false)
(set-ready-to-lock false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %)))
(if (> touch-timestamp-diff record-audio.constants/min-touch-duration) 0 250)))))
(reset! touch-active? false))
(when @reached-max-duration?
(reset! reached-max-duration? false))
(reset! touch-timestamp nil)))))
@@ -2,6 +2,7 @@
(:require
[clojure.string :as string]
[goog.string :as gstring]
[oops.core :as oops]
[quo.components.icon :as icons]
[quo.components.markdown.text :as text]
[quo.components.record-audio.record-audio.buttons.delete-button :as delete-button]
@@ -9,23 +10,93 @@
[quo.components.record-audio.record-audio.buttons.record-button :as record-button]
[quo.components.record-audio.record-audio.buttons.record-button-big :as record-button-big]
[quo.components.record-audio.record-audio.buttons.send-button :as send-button]
[quo.components.record-audio.record-audio.constants :as record-audio.constants]
[quo.components.record-audio.record-audio.handlers :as handlers]
[quo.components.record-audio.record-audio.style :as style]
[quo.components.record-audio.soundtrack.view :as soundtrack]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.audio-toolkit :as audio]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[taoensso.timbre :as log]
[utils.datetime :as datetime]))
(defn- recording-bar
(def ^:private min-audio-duration-ms 1000)
(def ^:private max-audio-duration-ms (if platform/ios? 120800 120500))
(def ^:private metering-interval 25)
(def ^:private base-filename "am")
(def ^:private default-format ".aac")
(def min-touch-duration 150)
(def ^:private record-button-area-big
{:width 56
:height 56
:x 64
:y 64})
(def ^:private record-button-area
{:width 48
:height 48
:x 68
:y 68})
(defn- delete-button-area
[{:keys [active? reviewing-audio?]}]
{:width (cond
active? 72
reviewing-audio? 32
:else 82)
:height (if reviewing-audio? 32 56)
:x (cond
active? -16
reviewing-audio? 36
:else -32)
:y (cond
active? 64
reviewing-audio? 76
:else 70)})
(defn- lock-button-area
[{:keys [active?]}]
{:width (if active? 72 100)
:height (if active? 72 102)
:x -32
:y -32})
(defn- send-button-area
[{:keys [active? reviewing-audio?]}]
{:width (if reviewing-audio? 32 56)
:height (cond
active? 72
reviewing-audio? 47
:else 92)
:x (if reviewing-audio? 76 32)
:y (cond
active? -16
reviewing-audio? 76
:else -32)})
(defn touch-inside-area?
[{:keys [location-x location-y ignore-min-y? ignore-max-y? ignore-min-x? ignore-max-x?]}
{:keys [width height x y]}]
(let [max-x (+ x width)
max-y (+ y height)]
(and
(and
(or ignore-min-x? (>= location-x x))
(or ignore-max-x? (<= location-x max-x)))
(and
(or ignore-min-y? (>= location-y y))
(or ignore-max-y? (<= location-y max-y))))))
(defn- f-recording-bar
[recording-length-ms ready-to-delete?]
(let [fill-percentage (/ (* recording-length-ms 100) record-audio.constants/max-audio-duration-ms)]
(let [fill-percentage (/ (* recording-length-ms 100) max-audio-duration-ms)]
[rn/view {:style (style/recording-bar-container)}
[rn/view {:style (style/recording-bar fill-percentage ready-to-delete?)}]]))
(defn- time-counter
(defn- f-time-counter
[recording? recording-length-ms ready-to-delete? reviewing-audio? audio-current-time-ms]
(let [s (quot (if recording? recording-length-ms audio-current-time-ms) 1000)
time-str (gstring/format "%02d:%02d" (quot s 60) (mod s 60))]
@@ -40,12 +111,10 @@
{:style (style/timer-text)}))
time-str]]))
(defn- play-button
[playing-audio? set-playing-audio player-ref playing-timer set-audio-current-time-ms seeking-audio?
set-seeking-audio
max-duration-ms]
(defn- f-play-button
[playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio? max-duration-ms]
(let [on-play (fn []
(set-playing-audio true)
(reset! playing-audio? true)
(reset! playing-timer
(js/setInterval
(fn []
@@ -53,24 +122,24 @@
player-state (audio/get-state @player-ref)
playing? (= player-state audio/PLAYING)]
(when (and playing?
(not seeking-audio?)
(not @seeking-audio?)
(> current-time 0)
(< current-time max-duration-ms))
(set-audio-current-time-ms current-time))
(reset! audio-current-time-ms current-time))
(when (>= current-time max-duration-ms)
(audio/stop-playing
@player-ref
(fn []
(set-playing-audio false)
(reset! playing-audio? false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil)
(set-audio-current-time-ms 0)
(set-seeking-audio false)))
(reset! audio-current-time-ms 0)
(reset! seeking-audio? false)))
#(log/error "[record-audio] stop player - error: " %)))))
100)))
on-pause (fn []
(set-playing-audio false)
(reset! playing-audio? false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil))
@@ -85,186 +154,445 @@
{:style (style/play-button)
:on-press on-press}
[icons/icon
(if playing-audio? :i/pause :i/play)
(if @playing-audio? :i/pause :i/play)
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]))
(defn record-audio
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio audio-file on-lock
max-duration-ms on-check-audio-permissions record-audio-permission-granted
on-request-record-audio-permission]}]
(let [;;STATE
[recording? set-recording] (rn/use-state false)
[locked? set-locked] (rn/use-state false)
[ready-to-send? set-ready-to-send] (rn/use-state false)
[ready-to-lock? set-ready-to-lock] (rn/use-state false)
[ready-to-delete?
set-ready-to-delete] (rn/use-state false)
[reviewing-audio?
set-reviewing-audio] (rn/use-state (some? audio-file))
[playing-audio?
set-playing-audio] (rn/use-state false)
[recording-length-ms
set-recording-length-ms] (rn/use-state 0)
[audio-current-time-ms
set-audio-current-time-ms] (rn/use-state 0)
[seeking-audio? set-seeking-audio] (rn/use-state false)
[force-show-controls?
set-force-show-controls] (rn/use-state (some? audio-file))
;;ATOMS
recording-start-ms (rn/use-ref-atom (datetime/timestamp))
clear-timeout (rn/use-ref-atom nil)
record-button-at-initial-position? (rn/use-ref-atom true)
record-button-is-animating? (rn/use-ref-atom false)
idle? (rn/use-ref-atom false)
recorder-ref (rn/use-ref-atom nil)
player-ref (rn/use-ref-atom nil)
touch-active? (rn/use-ref-atom false)
playing-timer (rn/use-ref-atom nil)
output-file (rn/use-ref-atom audio-file)
reached-max-duration? (rn/use-ref-atom false)
touch-timestamp (rn/use-ref-atom nil)
touch-identifier (rn/use-ref-atom nil)
disabled? (rn/use-ref-atom false)
app-state-listener (rn/use-ref-atom nil)
;;HANDLERS
destroy-player (rn/use-callback
(fn []
(audio/destroy-player @player-ref)
(reset! player-ref nil)))
reload-player (handlers/get-reload-player
[player-ref destroy-player set-playing-audio playing-timer
set-audio-current-time-ms set-seeking-audio])
recorder-on-meter (handlers/get-recorder-on-meter
[recording-start-ms set-recording-length-ms
reached-max-duration?
locked? set-locked set-reviewing-audio idle? set-recording
set-ready-to-lock set-ready-to-send set-ready-to-delete
recorder-ref
output-file reload-player
on-reviewing-audio])
reload-recorder (handlers/get-reload-recorder [recorder-ref
recorder-on-meter])
on-start-should-set-responder (handlers/get-on-start-should-set-responder
[locked? idle? disabled? recorder-on-meter touch-timestamp
touch-identifier
reviewing-audio? record-audio-permission-granted
set-recording set-playing-audio output-file
recorder-ref recording-start-ms set-audio-current-time-ms
on-start-recording
on-request-record-audio-permission touch-active?])
on-responder-move (handlers/get-on-responder-move
[locked? ready-to-send? set-ready-to-send
ready-to-delete? set-ready-to-delete
ready-to-lock? set-ready-to-lock
touch-identifier
record-button-at-initial-position?
recording?])
on-responder-release (handlers/get-on-responder-release
[idle? reached-max-duration? touch-timestamp
recording-length-ms set-recording-length-ms
reviewing-audio? set-reviewing-audio
set-audio-current-time-ms set-force-show-controls on-send
output-file player-ref
destroy-player on-cancel record-button-is-animating?
ready-to-lock? set-ready-to-lock
locked? set-locked on-lock ready-to-delete?
set-ready-to-delete ready-to-send? set-ready-to-send
disabled? on-reviewing-audio
recorder-ref set-recording reload-player recording-start-ms
touch-active?])]
(defn- record-audio-internal
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio
record-audio-permission-granted
on-request-record-audio-permission on-check-audio-permissions
audio-file on-lock max-duration-ms theme]}]
[:f>
;; TODO we need to refactor this, and use :f> with defined function, currenly state is reseted each
;; time parent component
;; is re-rendered
(fn []
(let [recording? (reagent/atom false)
locked? (reagent/atom false)
ready-to-send? (reagent/atom false)
ready-to-lock? (reagent/atom false)
ready-to-delete? (reagent/atom false)
reviewing-audio? (reagent/atom (some? audio-file))
playing-audio? (reagent/atom false)
recording-length-ms (reagent/atom 0)
audio-current-time-ms (reagent/atom 0)
seeking-audio? (reagent/atom false)
force-show-controls? (reagent/atom (some? audio-file))
recording-start-ms (atom (datetime/timestamp))
clear-timeout (atom nil)
record-button-at-initial-position? (atom true)
record-button-is-animating? (atom false)
idle? (atom false)
recorder-ref (atom nil)
player-ref (atom nil)
touch-active? (atom false)
playing-timer (atom nil)
output-file (atom audio-file)
reached-max-duration? (atom false)
touch-timestamp (atom nil)
touch-identifier (atom nil)
disabled? (atom false)
app-state-listener (atom nil)
rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename (datetime/timestamp) default-format)
:meteringInterval metering-interval})
destroy-player
(fn []
(audio/destroy-player @player-ref)
(reset! player-ref nil))
reload-player
(fn [audio-file]
(when @player-ref
(destroy-player))
(reset! player-ref
(audio/new-player
(or audio-file (:filename rec-options))
{:autoDestroy false
:continuesToPlayInBackground false
:category audio/PLAYBACK}
(fn []
(reset! playing-audio? false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil)
(reset! audio-current-time-ms 0)
(reset! seeking-audio? false)))))
(audio/prepare-player
@player-ref
#(log/debug "[record-audio] prepare player - success")
#(log/error "[record-audio] prepare player - error: " %)))
destroy-recorder
(fn []
(audio/destroy-recorder @recorder-ref)
(reset! recorder-ref nil))
recorder-on-meter
(fn []
(when @recording-start-ms
(let [now-ms (datetime/timestamp)
recording-duration (- now-ms
@recording-start-ms)]
(reset! recording-length-ms recording-duration)
(when (>= recording-duration max-audio-duration-ms)
(reset! reached-max-duration? (not @locked?))
(reset! reviewing-audio? true)
(reset! idle? false)
(reset! locked? false)
(reset! recording? false)
(reset! ready-to-lock? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path
@recorder-ref))
(reload-player nil)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path
@recorder-ref))))
(log/debug "[record-audio] new recorder - on meter"))))
reload-recorder
(fn []
(when @recorder-ref
(destroy-recorder))
(reset! recorder-ref (audio/new-recorder
rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))))
reset-recorder
(fn []
(reset! recording? false)
(reset! reviewing-audio? false)
(reset! locked? false)
(reset! ready-to-send? false)
(reset! ready-to-lock? false)
(reset! ready-to-delete? false)
(reset! audio-current-time-ms 0)
(reset! recording-length-ms 0)
(reset! seeking-audio? false)
(reset! playing-audio? false)
(reset! touch-active? false)
(reset! reached-max-duration? false)
(reset! output-file nil)
(reset! idle? false)
(reset! record-button-is-animating? false)
(reset! record-button-at-initial-position? true)
(when @clear-timeout
(js/clearTimeout @clear-timeout)
(reset! clear-timeout nil))
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil))
(reload-recorder))
on-start-should-set-responder
(fn [^js e]
(when-not (or @locked? @idle? (nil? e) @disabled?)
(let [pressed-record-button? (touch-inside-area?
{:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY")
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
record-button-area)
new-recorder (audio/new-recorder
rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))]
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
(reset! touch-identifier (oops/oget e "nativeEvent.identifier"))
(when-not @reviewing-audio?
(if record-audio-permission-granted
(do
(when (not @idle?)
(reset! recording? pressed-record-button?))
(when pressed-record-button?
(reset! playing-audio? false)
(reset! output-file nil)
(reset! recorder-ref new-recorder)
(audio/start-recording
new-recorder
(fn []
(reset! recording-start-ms (datetime/timestamp))
(reset! audio-current-time-ms 0)
(log/debug "[record-audio] start recording - success"))
#(log/error "[record-audio] start recording - error: " %))
(when on-start-recording
(on-start-recording))))
(when on-request-record-audio-permission
(on-request-record-audio-permission))))
(when record-audio-permission-granted
(reset! touch-active? true))))
(and (not @idle?) (not @disabled?)))
on-responder-move
(fn [^js e]
(when-not @locked?
(let [location-x (oops/oget e "nativeEvent.locationX")
location-y (oops/oget e "nativeEvent.locationY")
page-x (oops/oget e "nativeEvent.pageX")
page-y (oops/oget e "nativeEvent.pageY")
identifier (oops/oget e "nativeEvent.identifier")
moved-to-send-button? (touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? true
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? true}
(send-button-area
{:active? @ready-to-send?
:reviewing-audio? false}))
moved-to-delete-button? (touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? true
:ignore-min-x? true
:ignore-max-x? false}
(delete-button-area
{:active? @ready-to-delete?
:reviewing-audio? false}))
moved-to-lock-button? (touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
(lock-button-area {:active? @ready-to-lock?}))
moved-to-record-button? (and
(touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
record-button-area-big)
(not= location-x page-x)
(not= location-y page-y))]
(when (= identifier @touch-identifier)
(cond
(and
(or
(and moved-to-record-button? @ready-to-lock?)
(and (not @locked?) moved-to-lock-button? @record-button-at-initial-position?))
(not @ready-to-delete?)
(not @ready-to-send?)
@recording?)
(reset! ready-to-lock? moved-to-lock-button?)
(and
(or
(and moved-to-record-button? @ready-to-delete?)
(and moved-to-delete-button? @record-button-at-initial-position?))
(not @ready-to-lock?)
(not @ready-to-send?)
@recording?)
(reset! ready-to-delete? moved-to-delete-button?)
(and
(or
(and moved-to-record-button? @ready-to-send?)
(and moved-to-send-button? @record-button-at-initial-position?))
(not @ready-to-lock?)
(not @ready-to-delete?)
@recording?)
(reset! ready-to-send? moved-to-send-button?))))))
on-responder-release
(fn [^js e]
(when (and
(not @idle?)
(not @reached-max-duration?))
(let [touch-area {:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY")
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
on-record-button? (touch-inside-area?
touch-area
record-button-area-big)
on-send-button? (touch-inside-area?
touch-area
(send-button-area
{:active? false
:reviewing-audio? true}))
on-delete-button? (touch-inside-area?
touch-area
(delete-button-area
{:active? false
:reviewing-audio? true}))
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
audio-length @recording-length-ms]
(cond
(and @reviewing-audio? on-send-button?)
(do
(reset! reviewing-audio? false)
(reset! audio-current-time-ms 0)
(reset! force-show-controls? false)
(when on-send
(on-send {:file-path @output-file
:duration (min max-audio-duration-ms
(int (audio/get-player-duration @player-ref)))}))
(when @player-ref
(audio/stop-playing
@player-ref
(fn []
(destroy-player)
(log/debug "[record-audio] stop playing - success"))
#(log/error "[record-audio] stop playing - error: " %))))
(and @reviewing-audio? on-delete-button?)
(do
(reset! reviewing-audio? false)
(reset! audio-current-time-ms 0)
(reset! force-show-controls? false)
(destroy-player)
(when on-cancel
(on-cancel)))
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false)
(when on-lock
(on-lock)))
(and (not @reviewing-audio?)
(or on-record-button?
(and (not @ready-to-delete?)
(not @ready-to-lock?)
(not @ready-to-send?))))
(do
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
(js/setTimeout
(fn []
(if (>= @recording-length-ms min-audio-duration-ms)
(do (reset! reviewing-audio? true)
(reset! idle? false)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
(do (when on-cancel
(on-cancel))
(reset! idle? true)))
(reset! locked? false)
(reset! recording? false)
(reset! ready-to-lock? false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
(when (>= audio-length min-audio-duration-ms)
(reload-player nil))
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false))
(if (> touch-timestamp-diff min-touch-duration) 0 250)))
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
(do
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
(js/setTimeout
(fn []
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration @recording-length-ms}))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %)))
(if (> touch-timestamp-diff min-touch-duration) 0 250)))))
(reset! touch-active? false))
(when @reached-max-duration?
(reset! reached-max-duration? false))
(reset! touch-timestamp nil))]
(fn []
(rn/use-mount (fn []
(when on-check-audio-permissions
(on-check-audio-permissions))
(when on-init
(on-init reset-recorder))
(when audio-file
(let [filename (last (string/split audio-file "/"))]
(reload-player filename)))
(reset! app-state-listener
(.addEventListener rn/app-state
"change"
#(when (= % "background")
(reset! playing-audio? false))))
#(.remove @app-state-listener)))
[rn/view
{:style style/bar-container
:pointer-events :box-none}
(when @reviewing-audio?
[:<>
[:f> f-play-button playing-audio? player-ref playing-timer audio-current-time-ms
seeking-audio? max-duration-ms]
[:f> soundtrack/f-soundtrack
{:audio-current-time-ms audio-current-time-ms
:player-ref @player-ref
:seeking-audio? seeking-audio?
:max-audio-duration-ms max-duration-ms}]])
(when (or @recording? @reviewing-audio?)
[:f> f-time-counter @recording? @recording-length-ms @ready-to-delete? @reviewing-audio?
@audio-current-time-ms])
(when @recording?
[:f> f-recording-bar @recording-length-ms @ready-to-delete?])
[rn/view
{:test-ID "record-audio"
:style style/button-container
:hit-slop {:top -70
:bottom 0
:left 0
:right 0}
:pointer-events :box-only
:on-start-should-set-responder on-start-should-set-responder
:on-responder-move on-responder-move
:on-responder-release on-responder-release}
[:f> delete-button/f-delete-button recording? ready-to-delete? reviewing-audio?
@force-show-controls?]
[:f> lock-button/f-lock-button recording? ready-to-lock? locked?]
[:f> send-button/f-send-button recording? ready-to-send? reviewing-audio?
@force-show-controls?]
[:f> record-button-big/f-record-button-big
{:recording? recording?
:ready-to-send? ready-to-send?
:ready-to-lock? ready-to-lock?
:ready-to-delete? ready-to-delete?
:record-button-is-animating? record-button-is-animating?
:record-button-at-initial-position? record-button-at-initial-position?
:locked? locked?
:reviewing-audio? reviewing-audio?
:recording-length-ms recording-length-ms
:clear-timeout clear-timeout
:touch-active? touch-active?
:recorder-ref recorder-ref
:reload-recorder-fn reload-recorder
:idle? idle?
:on-send on-send
:on-cancel on-cancel
:theme theme}]
[:f> record-button/f-record-button recording? reviewing-audio?]]])))])
;;ON MOUNT
(rn/use-mount
(fn []
(when on-check-audio-permissions (on-check-audio-permissions))
(when on-init
(on-init
(fn reset-recorder []
(set-recording false)
(set-reviewing-audio false)
(set-locked false)
(set-ready-to-send false)
(set-ready-to-lock false)
(set-ready-to-delete false)
(set-audio-current-time-ms 0)
(set-recording-length-ms 0)
(set-seeking-audio false)
(set-playing-audio false)
(reset! touch-active? false)
(reset! reached-max-duration? false)
(reset! output-file nil)
(reset! idle? false)
(reset! record-button-is-animating? false)
(reset! record-button-at-initial-position? true)
(when @clear-timeout
(js/clearTimeout @clear-timeout)
(reset! clear-timeout nil))
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil))
(reload-recorder))))
(when audio-file
(let [filename (last (string/split audio-file "/"))]
(reload-player filename)))
(reset! app-state-listener
(.addEventListener rn/app-state
"change"
#(when (= % "background")
(set-playing-audio false))))))
;;ON UNMOUNT
(rn/use-unmount #(.remove @app-state-listener))
[rn/view
{:style style/bar-container
:pointer-events :box-none}
(when reviewing-audio?
[:<>
[play-button playing-audio? set-playing-audio player-ref playing-timer set-audio-current-time-ms
seeking-audio? set-seeking-audio max-duration-ms]
[soundtrack/soundtrack
{:audio-current-time-ms audio-current-time-ms
:set-audio-current-time-ms set-audio-current-time-ms
:player-ref @player-ref
:seeking-audio? seeking-audio?
:set-seeking-audio set-seeking-audio
:max-audio-duration-ms max-duration-ms}]])
(when (or recording? reviewing-audio?)
[time-counter recording? recording-length-ms ready-to-delete? reviewing-audio?
audio-current-time-ms])
(when recording?
[recording-bar recording-length-ms ready-to-delete?])
[rn/view
{:test-ID "record-audio"
:style style/button-container
:hit-slop {:top -70 :bottom 0 :left 0 :right 0}
:pointer-events :box-only
:on-start-should-set-responder on-start-should-set-responder
:on-responder-move on-responder-move
:on-responder-release on-responder-release}
[delete-button/delete-button recording? ready-to-delete? reviewing-audio? force-show-controls?]
[lock-button/lock-button recording? ready-to-lock? locked?]
[send-button/send-button recording? ready-to-send? reviewing-audio? force-show-controls?]
[record-button-big/record-button-big
{:recording? recording?
:set-recording set-recording
:ready-to-send? ready-to-send?
:set-ready-to-send set-ready-to-send
:ready-to-lock? ready-to-lock?
:set-ready-to-lock set-ready-to-lock
:ready-to-delete? ready-to-delete?
:set-ready-to-delete set-ready-to-delete
:record-button-is-animating? record-button-is-animating?
:record-button-at-initial-position? record-button-at-initial-position?
:locked? locked?
:set-locked set-locked
:reviewing-audio? reviewing-audio?
:recording-length-ms recording-length-ms
:set-recording-length-ms set-recording-length-ms
:clear-timeout clear-timeout
:touch-active? touch-active?
:recorder-ref recorder-ref
:reload-recorder-fn reload-recorder
:idle? idle?
:on-send on-send
:on-cancel on-cancel}]
[record-button/record-button recording? reviewing-audio?]]]))
(def record-audio (quo.theme/with-theme record-audio-internal))
@@ -2,6 +2,7 @@
(:require
[quo.components.record-audio.soundtrack.view :as soundtrack]
[react-native.audio-toolkit :as audio]
[reagent.core :as reagent]
[test-helpers.component :as h]))
(h/describe "soundtrack component"
@@ -9,21 +10,23 @@
(h/test "renders soundtrack"
(with-redefs [audio/get-player-duration (fn [] 2000)]
(h/render [soundtrack/soundtrack
{:player-ref {}
:audio-current-time-ms 0}])
(-> (h/expect (h/get-by-test-id "soundtrack"))
(.toBeTruthy))))
(let [player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(-> (h/expect (h/get-by-test-id "soundtrack"))
(.toBeTruthy)))))
(h/test "soundtrack on-sliding-start works"
(with-redefs [audio/get-player-duration (fn [] 2000)]
(let [seeking-audio? (atom false)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:set-seeking-audio #(reset! seeking-audio? %)
:player-ref {}
:audio-current-time-ms 0
:set-audio-current-time-ms #()}])
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:seeking-audio? seeking-audio?
:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
@@ -33,13 +36,13 @@
(h/test "soundtrack on-sliding-complete works"
(with-redefs [audio/get-player-duration (fn [] 2000)
audio/seek-player (js/jest.fn)]
(let [seeking-audio? (atom false)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:set-seeking-audio #(reset! seeking-audio? %)
:player-ref {}
:audio-current-time-ms 0
:set-audio-current-time-ms #()}])
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:seeking-audio? seeking-audio?
:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
@@ -55,14 +58,13 @@
(h/test "soundtrack on-value-change when seeking audio works"
(with-redefs [audio/get-player-duration (fn [] 2000)
audio/seek-player (js/jest.fn)]
(let [seeking-audio? (atom false)
audio-current-time-ms (atom 0)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:set-seeking-audio #(reset! seeking-audio? %)
:player-ref {}
:audio-current-time-ms audio-current-time-ms
:set-audio-current-time-ms #(reset! audio-current-time-ms %)}])
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:seeking-audio? seeking-audio?
:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
@@ -1,47 +1,42 @@
(ns quo.components.record-audio.soundtrack.view
(:require
[quo.components.icons.icons :as icons]
[quo.components.record-audio.soundtrack.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.audio-toolkit :as audio]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.slider :as slider]
[taoensso.timbre :as log]))
(def ^:private thumb-light (icons/icon-source :thumb-light12))
(def ^:private thumb-dark (icons/icon-source :thumb-dark12))
(def ^:private thumb-light (js/require "../resources/images/icons2/12x12/thumb-light.png"))
(def ^:private thumb-dark (js/require "../resources/images/icons2/12x12/thumb-dark.png"))
(defn soundtrack
[{:keys [audio-current-time-ms set-audio-current-time-ms player-ref style
seeking-audio? set-seeking-audio max-audio-duration-ms]}]
(let [audio-duration-ms (min max-audio-duration-ms (audio/get-player-duration player-ref))
theme (quo.theme/use-theme-value)
on-sliding-start (rn/use-callback #(set-seeking-audio true))
on-sliding-complete (rn/use-callback
(fn [seek-time]
(set-seeking-audio false)
(audio/seek-player
player-ref
seek-time
#(log/debug "[record-audio] on seek - seek time: " seek-time)
#(log/error "[record-audio] on seek - error: " %)))
[player-ref])
on-value-change (rn/use-callback #(when seeking-audio? (set-audio-current-time-ms %))
[seeking-audio?])]
[slider/slider
{:test-ID "soundtrack"
:style (merge (style/player-slider-container) style)
:minimum-value 0
:maximum-value audio-duration-ms
:value audio-current-time-ms
:on-sliding-start on-sliding-start
:on-sliding-complete on-sliding-complete
:on-value-change on-value-change
:thumb-image (quo.theme/theme-value thumb-light thumb-dark theme)
:minimum-track-tint-color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
:maximum-track-tint-color (colors/theme-colors
(if platform/ios? colors/neutral-20 colors/neutral-40)
(if platform/ios? colors/neutral-80 colors/neutral-60)
theme)}]))
(defn f-soundtrack
[{:keys [audio-current-time-ms player-ref style seeking-audio? max-audio-duration-ms]}]
(let [audio-duration-ms (min max-audio-duration-ms (audio/get-player-duration player-ref))
theme (quo.theme/use-theme-value)]
[:<>
[slider/slider
{:test-ID "soundtrack"
:style (merge
(style/player-slider-container)
(or style {}))
:minimum-value 0
:maximum-value audio-duration-ms
:value @audio-current-time-ms
:on-sliding-start #(reset! seeking-audio? true)
:on-sliding-complete (fn [seek-time]
(reset! seeking-audio? false)
(audio/seek-player
player-ref
seek-time
#(log/debug "[record-audio] on seek - seek time: " seek-time)
#(log/error "[record-audio] on seek - error: " %)))
:on-value-change #(when @seeking-audio?
(reset! audio-current-time-ms %))
:thumb-image (quo.theme/theme-value thumb-light thumb-dark theme)
:minimum-track-tint-color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
:maximum-track-tint-color (colors/theme-colors
(if platform/ios? colors/neutral-20 colors/neutral-40)
(if platform/ios? colors/neutral-80 colors/neutral-60)
theme)}]]))
@@ -16,9 +16,9 @@
(reagent/flush))
(defn- reorder-category-internal
[{:keys [label data blur? theme container-style]}]
[{:keys [label data blur? theme]}]
(reagent/with-let [atom-data (reagent/atom data)]
[rn/view {:style (merge (style/container label) container-style)}
[rn/view {:style style/container}
[text/text
{:weight :medium
:size :paragraph-2
@@ -23,7 +23,7 @@
(def ?profile
[:map
[:type [:= :profile]]
[:profile-picture [:maybe :schema.quo/profile-picture-source]]])
[:profile-picture :schema.common/image-source]])
(def ?wallet
[:map
@@ -150,7 +150,8 @@
profile-picture emoji on-share-press address]
:as props}]
[:<>
[gradient-cover/view {:customization-color customization-color :height 463}]
[rn/view {:style style/gradient-bg}
[gradient-cover/view {:customization-color customization-color :height 463}]]
[rn/view {:style style/content-container}
[rn/view {:style style/share-qr-container}
[rn/view {:style style/share-qr-inner-container}
@@ -1,6 +1,5 @@
(ns quo.components.wallet.account-permissions.view
(:require [clojure.string :as string]
[quo.components.avatars.account-avatar.view :as account-avatar]
(:require [quo.components.avatars.account-avatar.view :as account-avatar]
[quo.components.dividers.divider-line.view :as divider-line]
[quo.components.icon :as icons]
[quo.components.markdown.text :as text]
@@ -29,24 +28,17 @@
(i18n/label :t/no-relevant-tokens)]
(let [token-length (dec (count tokens))]
(map-indexed
(fn [idx
{:keys [type token amount collectible-name collectible-img-src
token-img-src]
collectible-symbol :symbol}]
^{:key idx}
[required-tokens/view
{:container-style style/token-and-text
:type type
:amount amount
:token token
:token-img-src token-img-src
:collectible-img-src collectible-img-src
:collectible-name (if (string/blank? collectible-name)
collectible-symbol
collectible-name)
:divider? (not= token-length idx)}])
tokens)))]]))
(map-indexed (fn [idx {:keys [type token amount collectible-name collectible-img-src]}]
^{:key idx}
[required-tokens/view
{:container-style style/token-and-text
:type type
:amount amount
:token token
:collectible-img-src collectible-img-src
:collectible-name collectible-name
:divider? (not= token-length idx)}])
tokens)))]]))
(defn- view-internal
[{:keys
@@ -10,5 +10,5 @@
[:format {:optional true} [:enum :short :long]]
[:theme :schema.common/theme]
[:networks {:optional true}
[:maybe [:sequential [:map [:network-name :keyword] [:short-name :string]]]]]]]]
[:maybe [:sequential [:map [:name :keyword] [:short-name :string]]]]]]]]
:any])
@@ -20,8 +20,7 @@
(let [network-text-xf (map #(colored-network-text theme %))
address-text [text/text
{:size :paragraph-2
;; TODO: monospace font
;; https://github.com/status-im/status-mobile/issues/17009
;; TODO: monospace font https://github.com/status-im/status-mobile/issues/17009
:weight :monospace
:style (style/address-text format blur? theme)}
(if (= format :short)
@@ -12,8 +12,9 @@
:type :default
:name "Trip to Vegas"
:address "0x0ah...71a"}
:networks [{:network-name :ethereum :short-name "eth"}
{:network-name :optimism :short-name "opt"}]
:networks [{:name :ethereum :short-name "eth"}
{:name :optimism :short-name "opt"}]
:state :default
:action :none}])
(def default-details
@@ -54,7 +55,7 @@
:action :selector
:details other-details}]
theme)
(h/is-truthy (h/get-by-label-text :radio-off)))
(h/is-truthy (h/get-by-label-text :radio-on)))
(h/test "Options action renders"
(h/render-with-theme-provider [keypair/view
+1 -7
View File
@@ -1,7 +1,6 @@
(ns quo.components.wallet.keypair.style
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
[quo.foundations.colors :as colors]))
(defn container
[{:keys [blur? customization-color theme selected?]}]
@@ -35,8 +34,3 @@
{:color (if blur?
colors/white-opa-40
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))})
(defn dot
[blur? theme]
(merge (subtitle blur? theme)
{:bottom (if platform/ios? 2 -2)}))
+9 -13
View File
@@ -11,6 +11,7 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
@@ -59,7 +60,7 @@
:accessibility-label :options-button}]])]))
(defn details-view
[{:keys [details stored type blur? theme]}]
[{:keys [details stored blur? theme]}]
(let [{:keys [address]} details]
[rn/view
{:style {:flex-direction :row
@@ -69,11 +70,10 @@
{:size :paragraph-2
:style (style/subtitle blur? theme)}
address]
(when (= type :default-keypair)
[text/text
{:size :paragraph-2
:style (style/dot blur? theme)}
" ∙ "])
[text/text
{:size :paragraph-2
:style (merge (style/subtitle blur? theme) {:bottom (if platform/ios? 2 -2)})}
" ∙ "]
[text/text
{:size :paragraph-2
:style (style/subtitle blur? theme)}
@@ -86,13 +86,9 @@
colors/white-opa-40
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}]])]))
(defn- acc-list-card
[item & _rest]
[account-list-card/view item])
(defn- view-internal
[{:keys [default-selected?]}]
(let [selected? (reagent/atom default-selected?)]
[]
(let [selected? (reagent/atom true)]
(fn [{:keys [accounts action container-style] :as props}]
[rn/pressable
{:style (merge (style/container (merge props {:selected? @selected?})) container-style)
@@ -106,7 +102,7 @@
[details-view props]]]
[rn/flat-list
{:data accounts
:render-fn acc-list-card
:render-fn account-list-card/view
:separator [rn/view {:style {:height 8}}]
:style {:padding-horizontal 8}}]])))

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