Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a40a7e3bb |
@@ -1,2 +1,2 @@
|
||||
{:hooks {:analyze-call {utils.i18n/label utils.i18n/label}}
|
||||
{:hooks {:analyze-call {utils.i18n/label hooks.core/i18n-label}}
|
||||
:linters {:status-im.linter/invalid-translation-keyword {:level :error}}}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
(ns hooks.core
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn i18n-label
|
||||
"Verify call to `i18n/label` pass the translation keyword qualified with `t`."
|
||||
[{:keys [node]}]
|
||||
(let [[_ translation-key-node & _] (:children node)]
|
||||
(when (and (api/keyword-node? translation-key-node)
|
||||
(not= "t" (-> translation-key-node api/sexpr namespace)))
|
||||
(api/reg-finding! (assoc (meta translation-key-node)
|
||||
:message "Translation keyword should be qualified with \"t\""
|
||||
:type :status-im.linter/invalid-translation-keyword)))))
|
||||
|
||||
(comment
|
||||
;; Valid
|
||||
(i18n-label {:node (api/parse-string "(i18n/label :t/foo)")})
|
||||
|
||||
;; Invalid
|
||||
(i18n-label {:node (api/parse-string "(i18n/label :foo)")}))
|
||||
@@ -1,26 +0,0 @@
|
||||
(ns utils.i18n
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn label
|
||||
"Verify call to `utils.i18n/label` pass the translation keyword qualified with `t`."
|
||||
[{:keys [node]}]
|
||||
(let [[_ translation-key-node & _] (:children node)]
|
||||
(when (and (hooks/keyword-node? translation-key-node)
|
||||
(not= "t" (-> translation-key-node hooks/sexpr namespace)))
|
||||
(hooks/reg-finding! (assoc (meta translation-key-node)
|
||||
:message "Translation keyword should be qualified with \"t\""
|
||||
:type :status-im.linter/invalid-translation-keyword)))))
|
||||
|
||||
(comment
|
||||
;; Valid
|
||||
(label {:node (hooks/parse-string "(i18n/label :t/foo {:var \"hello\"})")
|
||||
:cljc false
|
||||
:lang :cljs
|
||||
:filename ""
|
||||
:config {}
|
||||
:ns ""
|
||||
:context nil})
|
||||
|
||||
;; Invalid
|
||||
(label {:node (hooks/parse-string "(i18n/label :foo)")})
|
||||
)
|
||||
@@ -190,7 +190,3 @@ test/appium/tests/users.py
|
||||
|
||||
## git hooks
|
||||
lefthook.yml
|
||||
|
||||
## clj-kondo
|
||||
/.clj-kondo/taoensso/*
|
||||
/.clj-kondo/babashka/*
|
||||
|
||||
@@ -7,4 +7,3 @@ modules
|
||||
result
|
||||
target
|
||||
component-spec
|
||||
/app
|
||||
|
||||
@@ -274,11 +274,11 @@ run-re-frisk: ##@run Start re-frisk server
|
||||
# TODO: Migrate this to a Nix recipe, much the same way as nix/mobile/android/targets/release-android.nix
|
||||
run-android: export TARGET := android
|
||||
# INFO: If it's empty (no devices attached, parsing issues, script error) - for Nix it's the same as not set.
|
||||
run-android: export ANDROID_ABI_INCLUDE ?= $(shell ./scripts/adb_devices_abis.sh)
|
||||
run-android: export ANDROID_ABI_INCLUDE ?= $(shell ./scripts/adb_devices_arch.sh)
|
||||
run-android: ##@run Build Android APK and start it on the device
|
||||
npx react-native run-android --appIdSuffix debug
|
||||
|
||||
SIMULATOR=iPhone 13
|
||||
SIMULATOR=iPhone 11 Pro
|
||||
run-ios: export TARGET := ios
|
||||
run-ios: export IOS_STATUS_GO_TARGETS := iossimulator/amd64
|
||||
run-ios: ##@run Build iOS app and start it in a simulator/device
|
||||
@@ -312,10 +312,8 @@ lint: export TARGET := clojure
|
||||
lint: export CLJ_LINTER_PRINT_WARNINGS ?= false
|
||||
lint: ##@test Run code style checks
|
||||
@sh scripts/lint-re-frame-in-quo-components.sh && \
|
||||
sh scripts/lint-direct-require-component-outside-quo.sh && \
|
||||
clj-kondo --config .clj-kondo/config.edn --cache false --fail-level error --lint src $(if $(filter $(CLJ_LINTER_PRINT_WARNINGS),true),,| grep -v ': warning: ') && \
|
||||
ALL_CLOJURE_FILES=$(call find_all_clojure_files) && \
|
||||
scripts/lint_translations.clj && \
|
||||
zprint '{:search-config? true}' -sfc $$ALL_CLOJURE_FILES && \
|
||||
sh scripts/lint-trailing-newline.sh && \
|
||||
node_modules/.bin/prettier --write .
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
[Creating a pixel perfect UI](pixel-perfection.md)
|
||||
|
||||
[Contributing to status-go](status-go-changes.md)
|
||||
|
||||
## Testing
|
||||
|
||||
[How to run local tests](testing.md)
|
||||
|
||||
@@ -23,7 +23,7 @@ setups and runs the test suite once.
|
||||
setups and runs the test suite and watches for code changes will then retrigger the test suite.
|
||||
|
||||
## Writing Tests
|
||||
New test files will need their namespace added to either the file "src/quo/core_spec.cljs" or "src/status_im2/core_spec.cljs. These locations may update overtime but it is dependent on the entrypoint in shadow-cljs config discussed below.
|
||||
New test files will need their namespace added to either the file "src/quo2/core_spec.cljs" or "src/status_im2/core_spec.cljs. These locations may update overtime but it is dependent on the entrypoint in shadow-cljs config discussed below.
|
||||
|
||||
|
||||
### Best practices
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||

|
||||
|
||||
1. Export from figma 2 pngs 2x and 3x put them in `./resources/images/icons2`
|
||||
1. Export from figma 2 pngs 2x and 3x put them in `./resources/images/icons`
|
||||
2. if necessary, rename file so that filename contains only lower case chars, e.g. `"Icon-Name@2x.png"` should be renamed to `"icon_name@2x.png"`.
|
||||
3. In the app `icon_name.png` still can be accessed as `icon-name`, so in order to use can add the next code:
|
||||
```clojure
|
||||
|
||||
+22
-22
@@ -76,7 +76,7 @@ It's important to name functional components with `f-` prefix.
|
||||
|
||||
### Component props and API scheme to match Figma as closely as possible
|
||||
|
||||
Ideally, the prop names for components (particularly in quo Design System)
|
||||
Ideally, the prop names for components (particularly in quo2 Design System)
|
||||
should match the Figma properties as best as possible. This makes it easier for
|
||||
the developer using that component to configure it correctly for the screen it
|
||||
is being used on and avoids unnecessary overwrites and adjustments being made.
|
||||
@@ -134,7 +134,7 @@ For example if Figma has sizes `:small`, `:medium` and `:large`
|
||||
|
||||
Prefer to define styles in a separate file named `style.cljs`, colocated with
|
||||
the source file. For a real example, see
|
||||
[src/quo/components/record_audio/record_audio/style.cljs](../src/quo/components/record_audio/record_audio/style.cljs).
|
||||
[src/quo2/components/record_audio/record_audio/style.cljs](../src/quo2/components/record_audio/record_audio/style.cljs).
|
||||
|
||||
```clojure
|
||||
;; bad
|
||||
@@ -467,9 +467,9 @@ Prefer the pure version of `:json-rpc/call` (no callbacks).
|
||||
|
||||
### Registering event handlers
|
||||
|
||||
Register events with `utils.re-frame/reg-event-fx` and follow [re-frame's best
|
||||
Register events with `re-frame.core/reg-event-fx` and follow [re-frame's best
|
||||
practice](https://github.com/day8/re-frame/blob/39adca93673f334dc751ee2d99d340b51a9cc6db/docs/FAQs/BestPractice.md#use-the-fx-effect)
|
||||
so use only `:db` and `:fx` effects. `utils.re-frame/merge` and `utils.re-frame/defn` are deprecated and should not be
|
||||
so use only `:db` and `:fx` effects. `rf/merge` is deprecated and should not be
|
||||
used in the new code in `src/status_im2/`. Don't use
|
||||
`re-frame.core/reg-event-db`.
|
||||
|
||||
@@ -528,27 +528,27 @@ due to performance constraints.
|
||||
(:preferred-name multiaccount)))
|
||||
```
|
||||
|
||||
### Requiring quo components
|
||||
### Requiring quo2 components
|
||||
|
||||
Consume `quo` components from `quo.core`, unless the namespace is also inside
|
||||
the `quo/` directory.
|
||||
Consume `quo2` components from `quo2.core`, unless the namespace is also inside
|
||||
the `quo2/` directory.
|
||||
|
||||
```clojure
|
||||
;; bad
|
||||
(ns my-namespace
|
||||
(:require [quo.components.icon :as icon]))
|
||||
(:require [quo2.components.icon :as icon]))
|
||||
|
||||
(icon/icon :i/verified)
|
||||
|
||||
;; good
|
||||
(ns my-namespace
|
||||
(:require [quo.core :as quo]))
|
||||
(:require [quo2.core :as quo]))
|
||||
|
||||
(quo/icon :i/verified)
|
||||
|
||||
;; also good because both namespaces are inside quo/
|
||||
(ns quo.components.tabs.account-selector
|
||||
(:require [quo.components.markdown.text :as text]))
|
||||
;; also good because both namespaces are inside quo2/
|
||||
(ns quo2.components.tabs.account-selector
|
||||
(:require [quo2.components.markdown.text :as text]))
|
||||
```
|
||||
|
||||
### Require/import
|
||||
@@ -615,12 +615,12 @@ Use the appropriate keyword qualification/namespace.
|
||||
|
||||
```clojure
|
||||
;; bad
|
||||
(require '[quo.components.icon :as icons])
|
||||
(require '[quo2.components.icon :as icons])
|
||||
(icons/icon :main-icons2/verified)
|
||||
|
||||
;; good
|
||||
(require '[quo.core :as quo])
|
||||
(quo/icon :i/verified)
|
||||
(require '[quo2.core :as quo2])
|
||||
(quo2/icon :i/verified)
|
||||
```
|
||||
|
||||
### Translations
|
||||
@@ -695,7 +695,7 @@ First, the bird's-eye view with some example ClojureScript files:
|
||||
src
|
||||
├── js/
|
||||
├── mocks/
|
||||
├── quo
|
||||
├── quo2
|
||||
│ ├── components/
|
||||
│ ├── foundations/
|
||||
│ └── theme.cljs
|
||||
@@ -716,7 +716,7 @@ src
|
||||
|
||||
- `src/js`: Raw Javascript files, e.g. React Native Reanimated worklets.
|
||||
- `src/mocks`: Plumbing configuration to be able to run tests.
|
||||
- `src/quo/`: The component library for Status Mobile.
|
||||
- `src/quo2/`: The component library for Status Mobile.
|
||||
- `src/react_native/`: Contains only low-level constructs to help React Native
|
||||
work in tandem with Clojure(Script).
|
||||
- `src/status_im2/`: Directory where we try to be as strict as possible about
|
||||
@@ -728,7 +728,7 @@ src
|
||||
of the directory tree. Just like directories named `utils`, their directory
|
||||
nesting level communicates their applicable limits.
|
||||
- `src/status_im2/common/components/`: Contains reusable components that are not
|
||||
part of the design system (quo).
|
||||
part of the design system (quo2).
|
||||
- `src/status_im2/contexts/`: Contains [bounded contexts](#glossary), like
|
||||
`browser/`, `messaging/`, etc. As much as possible, _bounded contexts_ should
|
||||
not directly require each other's namespaces.
|
||||
@@ -743,9 +743,9 @@ directory nesting level precisely indicates its boundaries. For example, a
|
||||
`contexts/user_settings/utils/datetime.cljs` file communicates that it should
|
||||
only be used in the `user_settings` context.
|
||||
|
||||
### src/quo
|
||||
### src/quo2
|
||||
|
||||
The `src/quo/` directory holds all components for the new design system. As
|
||||
The `src/quo2/` directory holds all components for the new design system. As
|
||||
much as possible, its sub-directories and component names should reflect the
|
||||
same language used by designers.
|
||||
|
||||
@@ -753,14 +753,14 @@ Even though the directory lives alongside the rest of the codebase, we should
|
||||
think of it as an external entity that abstracts away particular Status domain
|
||||
knowledge.
|
||||
|
||||
Components inside `src/quo/` should not rely on re-frame, i.e. they should not
|
||||
Components inside `src/quo2/` should not rely on re-frame, i.e. they should not
|
||||
dispatch events or use subscriptions.
|
||||
|
||||
Example structure:
|
||||
|
||||
```
|
||||
src
|
||||
└── quo
|
||||
└── quo2
|
||||
├── components
|
||||
│ └── dropdown
|
||||
│ ├── style.cljs
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
# Pixel Perfection
|
||||
The Status Mobile team aims to align the design implementation perfectly according to a given screens respective Figma design spec.
|
||||
|
||||
Currently, the Figma Designs match up with both "iPhone 11 Pro" and "iPhone 13" screen size.
|
||||
|
||||
The Dimension reference for iPhone 11 Pro is 375 x 812 (width x height) and for iPhone 13 is 390 x 844.
|
||||
Currently the Figma Designs match up with an "iPhone 11 Pro" screen size.
|
||||
|
||||
To test your implementation is correct you can take the following steps
|
||||
|
||||
- Open a new Figma file so that you have write privileges.
|
||||
|
||||
- Copy in the component or screen from the Figma file.
|
||||
|
||||
- Using the "iPhone 11 Pro" or the "iPhone 13" simulator (it is the default now) you take a screenshot of your component and paste it into your Figma file.
|
||||
|
||||
- To get the right size, set your screenshot to width according to your simulator:
|
||||
- for iPhone 13 set 390
|
||||
- for iPhone 11 Pro set 375
|
||||
- Using the "iPhone 11 Pro" simulator (it is the default now) you take a screenshot of your component and paste it into your Figma file.
|
||||
|
||||
- To get the right size, set your screenshot to width 375px
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -30,23 +30,23 @@ You need to have an emulator like [AVD](https://developer.android.com/studio/run
|
||||
|
||||
#### Running on a simulator
|
||||
|
||||
We highly recommend using either the `iPhone 11 Pro` or `iPhone 13` simulator as its screen dimensions match with our design.
|
||||
We highly recommend using the `iPhone 11 Pro` simulator as its screen dimensions match with our design.
|
||||
|
||||
If you have Xcode `v12.x` (and above) installed in your system, you need to follow the below steps to add `iPhone 13` simulator:
|
||||
If you have Xcode `v12.x` (and above) installed in your system, you need to follow the below steps to add `iPhone 11 Pro` simulator:
|
||||
|
||||
1. Open Xcode
|
||||
2. Menu `>` Window `>` Devices and Simulators
|
||||
3. Tap `+` button on bottom left
|
||||
4. Select **Device Type** as `iPhone 13`
|
||||
4. Select **Device Type** as `iPhone 11 Pro`
|
||||
5. Leave the **Simulator Name** empty and tap on **Create**
|
||||
|
||||
##### NOTE ⚠️
|
||||
|
||||
Running `make run-ios` will target `iPhone 13` by default.
|
||||
Running `make run-ios` will target `iPhone 11 Pro` by default.
|
||||
|
||||
If you need to run on any other simulator, you can specify the simulator type by adding the `SIMULATOR` flag:
|
||||
```sh
|
||||
make run-ios SIMULATOR="iPhone 13"
|
||||
make run-ios SIMULATOR="iPhone 11 Pro"
|
||||
```
|
||||
|
||||
#### Running on a physical device
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# How to make changes to status-go
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
To make changes to status-go, the best approach is to first identify the issue/feature and the changes to be made directly in status-go.
|
||||
For any bugs we want to most likely write a failing test in status-go, and make sure that it passes, before we test on mobile.
|
||||
|
||||
Once you are quite confident that the status-go code is ready to be tested on mobile, you can test it locally by running the following command:
|
||||
|
||||
```
|
||||
env STATUS_GO_SRC_OVERRIDE={your-status-go-directory} make run-{android/ios}
|
||||
```
|
||||
|
||||
This will recompile status-go and run status-mobile on your device with that updated status-go version.
|
||||
|
||||
Once you have tested the status-go changes locally and you think it's ready for a PR:
|
||||
|
||||
1) Create a PR in status-go
|
||||
|
||||
2) Point your local status-mobile branch to that PR by running the script:
|
||||
|
||||
```
|
||||
scripts/update-status-go.sh {status-go-branch-name}
|
||||
```
|
||||
|
||||
This will update `status-go-version.json`
|
||||
|
||||
Commit updated status-go-version.json and create a status-mobile PR
|
||||
|
||||
Once both PRs have been approved and are good to merge, you can follow the [merge guidelines](./merging-pr-process.md)
|
||||
+1
-1
@@ -765,7 +765,7 @@ SPEC CHECKSUMS:
|
||||
RNLanguages: 962e562af0d34ab1958d89bcfdb64fafc37c513e
|
||||
RNPermissions: ad71dd4f767ec254f2cd57592fbee02afee75467
|
||||
RNReactNativeHapticFeedback: 2566b468cc8d0e7bb2f84b23adc0f4614594d071
|
||||
RNReanimated: 42f56dc5c032a11177b9ea12cdb57285318b432e
|
||||
RNReanimated: 62e43ee6baafb9ba3d3af1857d7fd23a1d41bff0
|
||||
RNShare: d82e10f6b7677f4b0048c23709bd04098d5aee6c
|
||||
RNStaticSafeAreaInsets: 055ddbf5e476321720457cdaeec0ff2ba40ec1b8
|
||||
RNSVG: 80584470ff1ffc7994923ea135a3e5ad825546b9
|
||||
|
||||
+40
@@ -1194,6 +1194,21 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||
return getPublicStorageDirectory().getAbsolutePath();
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
public String generateAlias(final String seed) {
|
||||
return Statusgo.generateAlias(seed);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void generateAliasAsync(final String seed, final Callback callback) throws JSONException {
|
||||
executeRunnableStatusGoMethod(() -> Statusgo.generateAlias(seed), callback);
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
public String identicon(final String seed) {
|
||||
return Statusgo.identicon(seed);
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
public String encodeTransfer(final String to, final String value) {
|
||||
return Statusgo.encodeTransfer(to, value);
|
||||
@@ -1254,6 +1269,31 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||
executeRunnableStatusGoMethod(() -> Statusgo.identicon(seed), callback);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void generateAliasAndIdenticonAsync(final String seed, final Callback callback) {
|
||||
|
||||
Log.d(TAG, "generateAliasAndIdenticonAsync");
|
||||
if (!checkAvailability()) {
|
||||
callback.invoke(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String resIdenticon = Statusgo.identicon(seed);
|
||||
String resAlias = Statusgo.generateAlias(seed);
|
||||
|
||||
Log.d(TAG, resIdenticon);
|
||||
Log.d(TAG, resAlias);
|
||||
callback.invoke(resAlias, resIdenticon);
|
||||
}
|
||||
};
|
||||
|
||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable
|
||||
Map<String, Object> getConstants() {
|
||||
|
||||
@@ -880,6 +880,10 @@ RCT_EXPORT_METHOD(callRPC:(NSString *)payload
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(generateAlias:(NSString *)publicKey) {
|
||||
return StatusgoGenerateAlias(publicKey);
|
||||
}
|
||||
|
||||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(keystoreDir) {
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *rootUrl =[[fileManager
|
||||
@@ -939,6 +943,19 @@ RCT_EXPORT_METHOD(initLogging:(BOOL)enabled
|
||||
callback(@[initResult]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(generateAliasAsync:(NSString *)publicKey
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
#if DEBUG
|
||||
NSLog(@"generateAliasAsync() method called");
|
||||
#endif
|
||||
NSString *result = StatusgoGenerateAlias(publicKey);
|
||||
callback(@[result]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(identicon:(NSString *)publicKey) {
|
||||
return StatusgoIdenticon(publicKey);
|
||||
}
|
||||
|
||||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(encodeTransfer:(NSString *)to
|
||||
value:(NSString *)value) {
|
||||
return StatusgoEncodeTransfer(to,value);
|
||||
@@ -994,6 +1011,15 @@ RCT_EXPORT_METHOD(validateMnemonic:(NSString *)seed
|
||||
callback(@[result]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(identiconAsync:(NSString *)publicKey
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
#if DEBUG
|
||||
NSLog(@"identiconAsync() method called");
|
||||
#endif
|
||||
NSString *result = StatusgoIdenticon(publicKey);
|
||||
callback(@[result]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(createAccountAndLogin:(NSString *)request) {
|
||||
#if DEBUG
|
||||
NSLog(@"createAccountAndLogin() method called");
|
||||
@@ -1008,6 +1034,16 @@ RCT_EXPORT_METHOD(restoreAccountAndLogin:(NSString *)request) {
|
||||
StatusgoRestoreAccountAndLogin(request);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(generateAliasAndIdenticonAsync:(NSString *)publicKey
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
#if DEBUG
|
||||
NSLog(@"generateAliasAndIdenticonAsync() method called");
|
||||
#endif
|
||||
NSString *identiconResult = StatusgoIdenticon(publicKey);
|
||||
NSString *aliasResult = StatusgoGenerateAlias(publicKey);
|
||||
callback(@[aliasResult, identiconResult]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(callPrivateRPC:(NSString *)payload
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
@@ -354,6 +354,38 @@ void _StopCPUProfiling(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
}
|
||||
|
||||
void _Identicon(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 Identicon")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the argument types
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'pk'")));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
|
||||
char *arg0 = *arg0Obj;
|
||||
|
||||
// Call exported Go function, which returns a C string
|
||||
char *c = Identicon(arg0);
|
||||
|
||||
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
|
||||
args.GetReturnValue().Set(ret);
|
||||
delete c;
|
||||
|
||||
}
|
||||
|
||||
void _EncodeTransfer(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
@@ -832,6 +864,38 @@ void _CreateAccountAndLogin(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
}
|
||||
|
||||
void _GenerateAlias(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 GenerateAlias")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the argument types
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'pk'")));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
|
||||
char *arg0 = *arg0Obj;
|
||||
|
||||
// Call exported Go function, which returns a C string
|
||||
char *c = GenerateAlias(arg0);
|
||||
|
||||
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
|
||||
args.GetReturnValue().Set(ret);
|
||||
delete c;
|
||||
|
||||
}
|
||||
|
||||
void _ValidateMnemonic(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
@@ -1889,6 +1953,7 @@ void init(Local<Object> exports) {
|
||||
NODE_SET_METHOD(exports, "multiAccountStoreAccount", _MultiAccountStoreAccount);
|
||||
NODE_SET_METHOD(exports, "initKeystore", _InitKeystore);
|
||||
NODE_SET_METHOD(exports, "stopCPUProfiling", _StopCPUProfiling);
|
||||
NODE_SET_METHOD(exports, "identicon", _Identicon);
|
||||
NODE_SET_METHOD(exports, "encodeTransfer", _EncodeTransfer);
|
||||
NODE_SET_METHOD(exports, "encodeFunctionCall", _EncodeFunctionCall);
|
||||
NODE_SET_METHOD(exports, "decodeParameters", _DecodeParameters);
|
||||
@@ -1903,6 +1968,7 @@ void init(Local<Object> exports) {
|
||||
NODE_SET_METHOD(exports, "resetChainData", _ResetChainData);
|
||||
NODE_SET_METHOD(exports, "saveAccountAndLogin", _SaveAccountAndLogin);
|
||||
NODE_SET_METHOD(exports, "createAccountAndLogin", _CreateAccountAndLogin);
|
||||
NODE_SET_METHOD(exports, "generateAlias", _GenerateAlias);
|
||||
NODE_SET_METHOD(exports, "validateMnemonic", _ValidateMnemonic);
|
||||
NODE_SET_METHOD(exports, "multiformatSerializePublicKey", _MultiformatSerializePublicKey);
|
||||
NODE_SET_METHOD(exports, "saveAccountAndLoginWithKeycard", _SaveAccountAndLoginWithKeycard);
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ let
|
||||
buildInputs = with pkgs; [
|
||||
clojure flock maven openjdk
|
||||
# lint specific utilities
|
||||
babashka clj-kondo clojure-lsp ripgrep zprint
|
||||
clj-kondo zprint clojure-lsp
|
||||
];
|
||||
# CLASSPATH from clojure deps with 'src' appended to find local sources.
|
||||
shellHook = with pkgs; ''
|
||||
|
||||
+1
-2
@@ -12,7 +12,7 @@
|
||||
"@react-native-async-storage/async-storage": "^1.17.9",
|
||||
"@react-native-camera-roll/camera-roll": "git+https://github.com/status-im/react-native-camera-roll.git#refs/tags/v5.1.1.1",
|
||||
"@react-native-community/audio-toolkit": "git+https://github.com/tbenr/react-native-audio-toolkit.git#refs/tags/v2.0.3-status-v6",
|
||||
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur.git#refs/tags/v4.3.3-status",
|
||||
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.3-status",
|
||||
"@react-native-community/clipboard": "^1.2.2",
|
||||
"@react-native-community/hooks": "^3.0.0",
|
||||
"@react-native-community/masked-view": "^0.1.6",
|
||||
@@ -47,7 +47,6 @@
|
||||
"react-native-image-crop-picker": "git+https://github.com/status-im/react-native-image-crop-picker.git#refs/tags/v0.36.2-status.0",
|
||||
"react-native-image-resizer": "^1.2.3",
|
||||
"react-native-image-viewing": "git+https://github.com/status-im/react-native-image-viewing.git#refs/tags/v0.2.1.status",
|
||||
"react-native-intersection-observer": "^0.2.0",
|
||||
"react-native-keychain": "git+https://github.com/status-im/react-native-keychain.git#refs/tags/v.3.0.0-5-status",
|
||||
"react-native-languages": "^3.0.2",
|
||||
"react-native-linear-gradient": "^2.8.0",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# quo components namespace ends with style|utils|types|reaction-resource
|
||||
# are not the component view, they are usually utils fns or styles
|
||||
|
||||
if rg --pcre2 --glob '!src/quo/**/*' 'quo\.components(\.[\w-]+)*\.[\w-]*(?<!style|utils|types|reaction-resource)(\s|\]|\n)' src; then
|
||||
echo "Found above direct require of quo component outside src/quo/"
|
||||
echo "For more info, check the style guide https://github.com/status-im/status-mobile/blob/develop/doc/new-guidelines.md#requiring-quo-components"
|
||||
exit 1
|
||||
elif [ $? -eq 1 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit $?
|
||||
fi
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env bb
|
||||
|
||||
(ns lint-translations
|
||||
(:require [babashka.pods :as pods]))
|
||||
|
||||
(pods/load-pod 'clj-kondo/clj-kondo "2023.09.07")
|
||||
(require '[pod.borkdude.clj-kondo :as kondo])
|
||||
(require '[cheshire.core :as json])
|
||||
(require '[clojure.set :as set])
|
||||
|
||||
(def src-paths ["src"])
|
||||
|
||||
(def translation-file "translations/en.json")
|
||||
|
||||
;; set the following to true when solving https://github.com/status-im/status-mobile/issues/17811
|
||||
(def flag-show-non-namespaced-translation-keys false)
|
||||
(def flag-show-non-namespaced-translation-keys-occurrences false) ;; this makes the output super verbose!
|
||||
|
||||
;; set the following to true when solving https://github.com/status-im/status-mobile/issues/17813
|
||||
;; and keep it permanently on after #17811 and #17813 have both been solved
|
||||
(def flag-show-unused-translation-keys false)
|
||||
|
||||
(def flag-show-missing-translation-keys true)
|
||||
|
||||
(defn- safe-name
|
||||
[x]
|
||||
(when x (name x)))
|
||||
|
||||
(defn- ->keyword
|
||||
[analysis-keyword]
|
||||
(keyword (safe-name (:ns analysis-keyword)) (:name analysis-keyword)))
|
||||
|
||||
(defn- report-issues
|
||||
[incorrect-usages]
|
||||
(doseq [incorrect-usage incorrect-usages]
|
||||
(->> incorrect-usage
|
||||
((juxt :filename :row :reason ->keyword))
|
||||
(apply format "%s:%s %s %s")
|
||||
println)))
|
||||
|
||||
(defn- extract-translation-keys
|
||||
[file]
|
||||
(-> file slurp json/parse-string keys))
|
||||
|
||||
(def ^:private probably-unused-warning
|
||||
(format "Probably unused translation key in %s:" translation-file))
|
||||
|
||||
(defn -main
|
||||
[& _args]
|
||||
(println "Linting translations...")
|
||||
(let [result (kondo/run!
|
||||
{:lint src-paths
|
||||
:config {:output {:analysis {:keywords true}}}})
|
||||
all-keywords (get-in result [:analysis :keywords])
|
||||
used-translations (filter (comp (partial = 't) :ns) all-keywords)
|
||||
file-translation-keys (apply sorted-set (extract-translation-keys translation-file))
|
||||
missing-translations (remove (comp file-translation-keys :name) used-translations)
|
||||
used-translation-keys (set (map :name used-translations))
|
||||
possibly-unused-translation-keys (set/difference file-translation-keys used-translation-keys)
|
||||
non-namespaced-translations (filter
|
||||
(fn [kw]
|
||||
(and (not (:ns kw))
|
||||
(possibly-unused-translation-keys (:name kw))))
|
||||
all-keywords)
|
||||
unused-translation-keys (set/difference possibly-unused-translation-keys
|
||||
(set (map :name non-namespaced-translations)))]
|
||||
|
||||
;; TODO (2023-11-06 akatov): delete the following once #17811 and #17813 have both been solved
|
||||
(doseq [k (apply sorted-set (map :name non-namespaced-translations))]
|
||||
(when flag-show-non-namespaced-translation-keys
|
||||
(println "Probably non-namespaced key" k))
|
||||
(when flag-show-non-namespaced-translation-keys-occurrences
|
||||
(->> non-namespaced-translations
|
||||
(filter #(= k (:name %)))
|
||||
(map #(assoc % :reason "Possibly non-namespaced translation key"))
|
||||
report-issues)))
|
||||
|
||||
(when flag-show-unused-translation-keys
|
||||
(run! #(println probably-unused-warning %) unused-translation-keys))
|
||||
|
||||
(when flag-show-missing-translation-keys
|
||||
(report-issues (map #(assoc % :reason "Undefined Translation Key") missing-translations)))
|
||||
|
||||
(if (and
|
||||
(or (not flag-show-missing-translation-keys)
|
||||
(empty? missing-translations))
|
||||
(or (not flag-show-unused-translation-keys)
|
||||
(empty? possibly-unused-translation-keys))
|
||||
(or (not flag-show-non-namespaced-translation-keys)
|
||||
(not flag-show-non-namespaced-translation-keys-occurrences)
|
||||
(empty? unused-translation-keys)))
|
||||
0
|
||||
1)))
|
||||
|
||||
(when (= *file* (System/getProperty "babashka.file"))
|
||||
(->> *command-line-args*
|
||||
(apply -main)
|
||||
System/exit))
|
||||
@@ -300,11 +300,6 @@
|
||||
{:clamp nil
|
||||
:withPause (fn [])})
|
||||
|
||||
(def react-native-intersection-observer
|
||||
#js
|
||||
{:InView #js {}
|
||||
:IOFlatList #js {}})
|
||||
|
||||
(def react-native-languages
|
||||
(clj->js {:default {:language "en"
|
||||
:addEventListener (fn [])
|
||||
@@ -431,7 +426,6 @@
|
||||
"react-native-transparent-video" react-native-transparent-video
|
||||
"react-native-orientation-locker" react-native-orientation-locker
|
||||
"react-native-gifted-charts" react-native-gifted-charts
|
||||
"react-native-intersection-observer" react-native-intersection-observer
|
||||
"../resources/data/emojis/en.json" (js/JSON.parse (slurp
|
||||
"./resources/data/emojis/en.json"))
|
||||
"../src/js/worklets/core.js" worklet-factory
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
[clojure.string :as string]
|
||||
[react-native.platform :as platform]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.transforms :as types]))
|
||||
[utils.transforms :as types]
|
||||
[utils.validators :as validators]))
|
||||
|
||||
(defn status
|
||||
[]
|
||||
@@ -430,6 +431,19 @@
|
||||
;; in unknown scenarios we also consider the device rooted to avoid degrading security
|
||||
:else (callback true)))
|
||||
|
||||
(defn generate-gfycat
|
||||
"Generate a 3 words random name based on the user public-key, synchronously"
|
||||
[public-key]
|
||||
(log/debug "[native-module] generate-gfycat")
|
||||
(when (validators/valid-public-key? public-key)
|
||||
(.generateAlias ^js (status) public-key)))
|
||||
|
||||
(defn identicon
|
||||
"Generate a icon based on a string, synchronously"
|
||||
[seed]
|
||||
(log/debug "[native-module] identicon")
|
||||
(.identicon ^js (status) seed))
|
||||
|
||||
(defn encode-transfer
|
||||
[to-norm amount-hex]
|
||||
(log/debug "[native-module] encode-transfer")
|
||||
@@ -487,6 +501,12 @@
|
||||
(log/debug "[native-module] to-checksum-address")
|
||||
(.toChecksumAddress ^js (status) address))
|
||||
|
||||
(defn gfycat-identicon-async
|
||||
"Generate an icon based on a string and 3 words random name asynchronously"
|
||||
[seed callback]
|
||||
(log/debug "[native-module] gfycat-identicon-async")
|
||||
(.generateAliasAndIdenticonAsync ^js (status) seed callback))
|
||||
|
||||
(defn validate-mnemonic
|
||||
"Validate that a mnemonic conforms to BIP39 dictionary/checksum standards"
|
||||
[mnemonic callback]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require
|
||||
[oops.core :as oops]
|
||||
[quo.components.animated-header-flatlist.style :as style]
|
||||
[quo.components.icon :as icon]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
@@ -73,11 +73,11 @@
|
||||
{:active-opacity 1
|
||||
:on-press back-button-on-press
|
||||
:style (style/button-container {:left 20 :theme theme})}
|
||||
[icon/icon :i/arrow-left {:size 20 :color (colors/theme-colors colors/black colors/white theme)}]]
|
||||
[quo/icon :i/arrow-left {:size 20 :color (colors/theme-colors colors/black colors/white theme)}]]
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:style (style/button-container {:right 20 :theme theme})}
|
||||
[icon/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white theme)}]]
|
||||
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white theme)}]]
|
||||
[reanimated/blur-view
|
||||
{:blurAmount 32
|
||||
:blurType :light
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
(ns quo.components.avatars.wallet-user-avatar
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(def circle-sizes
|
||||
{:small 20
|
||||
:medium 32
|
||||
:large 48
|
||||
:size-64 64
|
||||
:x-large 80})
|
||||
|
||||
(def font-sizes
|
||||
{:small :label
|
||||
:medium :paragraph-2
|
||||
:large :paragraph-1
|
||||
:size-64 :heading-1
|
||||
:x-large :heading-1})
|
||||
|
||||
(def font-weights
|
||||
{:small :medium
|
||||
:medium :semi-bold
|
||||
:large :semi-bold
|
||||
:size-64 :medium
|
||||
:x-large :medium})
|
||||
|
||||
(defn- view-internal
|
||||
"params, first name, last name, customization-color, size
|
||||
and if it's dark or not!"
|
||||
[{:keys [f-name l-name customization-color size theme monospace? uppercase?]
|
||||
:or {f-name "John"
|
||||
l-name "Doe"
|
||||
size :x-large
|
||||
uppercase? true}}]
|
||||
(let [circle-size (size circle-sizes)
|
||||
small? (= size :small)
|
||||
f-name-initial (-> f-name
|
||||
(#(if uppercase? (string/upper-case %) %))
|
||||
(subs 0 1))
|
||||
l-name-initial (-> l-name
|
||||
(#(if uppercase? (string/upper-case %) %))
|
||||
(subs 0 1))
|
||||
circle-color (if customization-color
|
||||
(colors/resolve-color customization-color theme 20)
|
||||
(colors/theme-colors colors/neutral-80-opa-5 colors/white-opa-5 theme))
|
||||
text-color (if customization-color
|
||||
(colors/resolve-color customization-color theme)
|
||||
(colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 theme))]
|
||||
[rn/view
|
||||
{:style {:width circle-size
|
||||
:height circle-size
|
||||
:border-radius circle-size
|
||||
:text-align :center
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color circle-color}}
|
||||
[text/text
|
||||
{:size (size font-sizes)
|
||||
:weight (if monospace? :monospace (size font-weights))
|
||||
:style {:color text-color}}
|
||||
(if small?
|
||||
(str f-name-initial)
|
||||
(str f-name-initial l-name-initial))]]))
|
||||
|
||||
(def wallet-user-avatar (quo.theme/with-theme view-internal))
|
||||
@@ -1,16 +0,0 @@
|
||||
(ns quo.components.avatars.wallet-user-avatar.component-spec
|
||||
(:require [quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "wallet user avatar"
|
||||
(h/describe "View internal"
|
||||
(h/test "Renders by default even with no input parameters"
|
||||
(h/render
|
||||
[wallet-user-avatar/wallet-user-avatar {}])
|
||||
(h/is-truthy (h/query-by-label-text :wallet-user-avatar)))
|
||||
|
||||
(h/test "Renders user’s initials when full name is provided"
|
||||
(h/render
|
||||
[wallet-user-avatar/wallet-user-avatar {:full-name "Jane Smith"}])
|
||||
(h/is-truthy (h/get-by-text "JS")))))
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
(ns quo.components.avatars.wallet-user-avatar.style
|
||||
(:require [quo.foundations.colors :as colors]))
|
||||
|
||||
(defn- circle-color
|
||||
[customization-color]
|
||||
(colors/custom-color customization-color 50 20))
|
||||
|
||||
(defn- text-color
|
||||
[customization-color theme]
|
||||
(colors/theme-colors
|
||||
(colors/custom-color customization-color 50)
|
||||
(colors/custom-color customization-color 60)
|
||||
theme))
|
||||
|
||||
(defn container
|
||||
[circle-size customization-color]
|
||||
{:width circle-size
|
||||
:height circle-size
|
||||
:border-radius circle-size
|
||||
:text-align :center
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color (circle-color customization-color)})
|
||||
|
||||
(defn text
|
||||
[customization-color theme]
|
||||
{:color (text-color customization-color theme)})
|
||||
@@ -1,57 +0,0 @@
|
||||
(ns quo.components.avatars.wallet-user-avatar.view
|
||||
(:require [quo.components.avatars.wallet-user-avatar.style :as style]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
utils.string))
|
||||
|
||||
(def properties
|
||||
{:size-20 {:size 20
|
||||
:font-size :label
|
||||
:font-weight :medium}
|
||||
:size-24 {:size 24
|
||||
:font-size :label
|
||||
:font-weight :semi-bold}
|
||||
:size-32 {:size 32
|
||||
:font-size :paragraph-2
|
||||
:font-weight :semi-bold}
|
||||
:size-48 {:size 48
|
||||
:font-size :paragraph-1
|
||||
:font-weight :semi-bold}
|
||||
:size-64 {:size 64
|
||||
:font-size :paragraph-1
|
||||
:font-weight :medium}
|
||||
:size-80 {:size 80
|
||||
:font-size :heading-1
|
||||
:font-weight :medium}})
|
||||
|
||||
(def smallest-possible (first (keys properties)))
|
||||
(def second-smallest-possible (second (keys properties)))
|
||||
(defn check-if-size-small
|
||||
[size]
|
||||
(or (= size smallest-possible)
|
||||
(= size second-smallest-possible)))
|
||||
(def biggest-possible (last (keys properties)))
|
||||
|
||||
(defn- view-internal
|
||||
"Options:
|
||||
|
||||
:full-name - string (default: nil) - used to generate initials
|
||||
:customization-color - keyword (default: nil) - color of the avatar
|
||||
:size - keyword (default: last element of properties object) - size of the
|
||||
avatar
|
||||
:monospace? - boolean (default: false) - use monospace font"
|
||||
[{:keys [full-name customization-color size theme monospace?]
|
||||
:or {size biggest-possible}}]
|
||||
(let [circle-size (:size (size properties))
|
||||
small? (check-if-size-small size)]
|
||||
[rn/view
|
||||
{:style (style/container circle-size customization-color)}
|
||||
[text/text
|
||||
{:accessibility-label :wallet-user-avatar
|
||||
:size (:font-size (size properties))
|
||||
:weight (if monospace? :monospace (:font-weight (size properties)))
|
||||
:style (style/text customization-color theme)}
|
||||
(utils.string/get-initials full-name (if small? 1 2))]]))
|
||||
|
||||
(def wallet-user-avatar (quo.theme/with-theme view-internal))
|
||||
@@ -1,3 +1,3 @@
|
||||
(ns quo.components.colors.color.constants)
|
||||
|
||||
(def ^:const color-size 48)
|
||||
(def ^:const IPHONE_11_PRO_VIEWPORT_WIDTH 375)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
(ns quo.components.colors.color.style
|
||||
(:require
|
||||
[quo.components.colors.color.constants :as constants]
|
||||
[quo.foundations.colors :as colors]
|
||||
[utils.responsiveness :as responsiveness]))
|
||||
|
||||
(defn color-button-common
|
||||
[window-width]
|
||||
{:width constants/color-size
|
||||
:height constants/color-size
|
||||
{:width 48
|
||||
:height 48
|
||||
:border-width 4
|
||||
:border-radius 24
|
||||
:margin-right (-> window-width
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "color-picker"
|
||||
(h/test "color picker rendered"
|
||||
(h/render [color-picker/view])
|
||||
(-> (h/expect (h/get-all-by-label-text :color-picker-item))
|
||||
(.toHaveLength 11)))
|
||||
(h/test "clicks on a color item"
|
||||
(let [event (h/mock-fn)]
|
||||
(h/render [color-picker/view {:on-change #(event)}])
|
||||
@@ -16,6 +20,11 @@
|
||||
(h/render [color-picker/view {:on-change #(reset! selected %)}])
|
||||
(h/fire-event :press (get (h/get-all-by-label-text :color-picker-item) 0))
|
||||
(-> (h/expect @selected)
|
||||
(.toStrictEqual :blue)))))
|
||||
(.toStrictEqual :blue))))
|
||||
(h/test "all of the values of colors-list are rendered"
|
||||
(h/render [color-picker/view])
|
||||
(js/Promise.all (map (fn [color]
|
||||
(h/is-truthy (h/get-all-by-label-text color)))
|
||||
color-picker/color-list))))
|
||||
|
||||
|
||||
|
||||
@@ -1,67 +1,39 @@
|
||||
(ns quo.components.colors.color-picker.view
|
||||
(:require
|
||||
[quo.components.colors.color.constants :as constants]
|
||||
[quo.components.colors.color.view :as color]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def color-list
|
||||
[:blue :yellow :purple :turquoise :magenta :sky :orange :army :flamingo :camel :copper])
|
||||
|
||||
(defn- on-change-handler
|
||||
[selected color-name on-change]
|
||||
(reset! selected color-name)
|
||||
(when on-change (on-change color-name)))
|
||||
|
||||
(defn get-item-layout
|
||||
[_ index]
|
||||
#js
|
||||
{:length constants/color-size
|
||||
:offset (* (+ constants/color-size 8) index)
|
||||
:index index})
|
||||
|
||||
(defn- view-internal
|
||||
(defn view
|
||||
"Options
|
||||
- `default-selected` Default selected color name.
|
||||
- `on-change` Callback called when a color is selected `(fn [color-name])`.
|
||||
- `blur?` Boolean to enable blur background support.}"
|
||||
[{:keys [default-selected blur? on-change feng-shui? container-style]}]
|
||||
[{:keys [default-selected]}]
|
||||
(let [selected (reagent/atom default-selected)
|
||||
{window-width :width} (rn/get-window)
|
||||
ref (atom nil)]
|
||||
(rn/use-effect #(js/setTimeout (fn []
|
||||
(.scrollToIndex ^js @ref
|
||||
#js
|
||||
{:animated true
|
||||
:index (.indexOf colors/account-colors
|
||||
default-selected)
|
||||
:viewPosition 0.5}))
|
||||
50))
|
||||
[rn/flat-list
|
||||
{:ref #(reset! ref %)
|
||||
;; TODO: using :feng-shui? temporarily while b & w is being developed.
|
||||
;; https://github.com/status-im/status-mobile/discussions/16676
|
||||
:data (if feng-shui?
|
||||
(conj colors/account-colors :feng-shui)
|
||||
colors/account-colors)
|
||||
:render-fn (fn [color idx]
|
||||
[color/view
|
||||
{:selected? (= color @selected)
|
||||
:on-press (fn [e]
|
||||
(.scrollToIndex ^js @ref
|
||||
#js
|
||||
{:animated true
|
||||
:index idx
|
||||
:viewPosition 0.5})
|
||||
(on-change-handler selected e on-change))
|
||||
:blur? blur?
|
||||
:key color
|
||||
:color color
|
||||
:idx idx
|
||||
:window-width window-width}])
|
||||
:get-item-layout get-item-layout
|
||||
:horizontal true
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:content-container-style container-style}]))
|
||||
|
||||
(defn view
|
||||
[props]
|
||||
[:f> view-internal props])
|
||||
{window-width :width} (rn/get-window)]
|
||||
(fn [{:keys [blur? on-change feng-shui? container-style]}]
|
||||
[rn/scroll-view
|
||||
{:horizontal true
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:content-container-style container-style}
|
||||
(doall (map-indexed (fn [idx color]
|
||||
[color/view
|
||||
{:selected? (= color @selected)
|
||||
:on-press #(on-change-handler selected % on-change)
|
||||
:blur? blur?
|
||||
:key color
|
||||
:color color
|
||||
:idx idx
|
||||
:window-width window-width}])
|
||||
;; TODO: using :feng-shui? temporarily while b & w is being developed.
|
||||
;; https://github.com/status-im/status-mobile/discussions/16676
|
||||
(if feng-shui? (conj color-list :feng-shui) color-list)))])))
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
(ns quo.components.community.community-stat.component-spec
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[test-helpers.component :as h]
|
||||
utils.money))
|
||||
|
||||
(h/describe "Community Stat Component"
|
||||
(h/test "default render"
|
||||
(h/render [quo/community-stat
|
||||
{:value "5000"
|
||||
:icon :i/active-members
|
||||
:accessibility-label :community-active-members}])
|
||||
(h/is-truthy (h/query-by-label-text :community-active-members)))
|
||||
(h/test "renders community stat with a value"
|
||||
(h/render [quo/community-stat
|
||||
{:value "5000"
|
||||
:icon :i/members}])
|
||||
(h/is-truthy (h/query-by-text (utils.money/format-amount "5000")))))
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns quo.components.community.community-stat.style
|
||||
(:require [quo.foundations.colors :as colors]))
|
||||
|
||||
(def container
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:height 22})
|
||||
|
||||
(defn text
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/black colors/white theme)
|
||||
:margin-left 2})
|
||||
@@ -1,26 +0,0 @@
|
||||
(ns quo.components.community.community-stat.view
|
||||
(:require [quo.components.community.community-stat.style :as style]
|
||||
[quo.components.icon :as quo.icons]
|
||||
[quo.components.markdown.text :as quo.text]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
utils.money))
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [value icon theme style accessibility-label]}]
|
||||
[rn/view
|
||||
{:style (merge style/container style)
|
||||
:accessibility-label accessibility-label}
|
||||
[quo.icons/icon icon
|
||||
{:size 16
|
||||
:container-style {:align-items :center
|
||||
:justify-content :center}
|
||||
:resize-mode :center
|
||||
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}]
|
||||
[quo.text/text
|
||||
{:size :paragraph-1
|
||||
:weight :regular
|
||||
:style (style/text theme)} (utils.money/format-amount value)]])
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
@@ -1,30 +1,52 @@
|
||||
(ns quo.components.community.community-view
|
||||
(:require
|
||||
[quo.components.community.community-stat.view :as community-stat]
|
||||
[quo.components.community.style :as style]
|
||||
[quo.components.icon :as icons]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.tags.permission-tag :as permission]
|
||||
[quo.components.tags.tag :as tag]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.gesture :as gesture]))
|
||||
[react-native.gesture :as gesture]
|
||||
utils.money))
|
||||
|
||||
(defn community-stats
|
||||
[{:keys [icon members-count icon-color accessibility-label]}]
|
||||
[rn/view
|
||||
{:accessibility-label accessibility-label
|
||||
:style (style/stats-count-container)}
|
||||
[rn/view {:margin-right 2}
|
||||
[icons/icon icon
|
||||
{:container-style {:align-items :center
|
||||
:justify-content :center}
|
||||
:resize-mode :center
|
||||
:size 16
|
||||
:color icon-color}]]
|
||||
[text/text
|
||||
{:weight :regular
|
||||
:size :paragraph-2}
|
||||
members-count]])
|
||||
|
||||
(defn community-stats-column
|
||||
[{:keys [type members-count active-count]}]
|
||||
[rn/view
|
||||
(if (= type :card-view)
|
||||
(style/card-stats-container)
|
||||
(style/list-stats-container))
|
||||
[community-stat/view
|
||||
{:accessibility-label :stats-members-count
|
||||
:icon :i/group
|
||||
:value members-count
|
||||
:style {:margin-right 12}}]
|
||||
[community-stat/view
|
||||
{:accessibility-label :stats-active-count
|
||||
:icon :i/active-members
|
||||
:value active-count}]])
|
||||
[{:keys [type theme blur? members-count active-count]}]
|
||||
(let [icon-color (if (and (= :dark theme) blur?)
|
||||
colors/white-opa-40
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))]
|
||||
[rn/view
|
||||
(if (= type :card-view)
|
||||
(style/card-stats-container)
|
||||
(style/list-stats-container))
|
||||
[community-stats
|
||||
{:accessibility-label :stats-members-count
|
||||
:icon :i/group
|
||||
:members-count (utils.money/format-amount members-count)
|
||||
:icon-color icon-color}]
|
||||
[community-stats
|
||||
{:accessibility-label :stats-active-count
|
||||
:icon :i/active-members
|
||||
:members-count (utils.money/format-amount active-count)
|
||||
:icon-color icon-color}]]))
|
||||
|
||||
(defn community-tags
|
||||
[{:keys [tags container-style last-item-style]}]
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
(def detail-container
|
||||
{:flex 1})
|
||||
|
||||
(defn stats-count-container
|
||||
[]
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:margin-right 16})
|
||||
|
||||
(defn card-stats-container
|
||||
[]
|
||||
{:flex-direction :row})
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(def default-props
|
||||
{:button-one-type :primary
|
||||
:button-two-type :grey
|
||||
:customization-color :blue})
|
||||
{:button-one-type :primary
|
||||
:button-two-type :grey})
|
||||
|
||||
(defn- view-internal
|
||||
"Options:
|
||||
@@ -24,9 +23,9 @@
|
||||
:scroll? - bool (default false) - Whether the iOS Home Indicator should be rendered"
|
||||
[props]
|
||||
(let [{:keys [actions description button-one-label button-two-label
|
||||
button-one-props button-two-props theme scroll? customization-color container-style]}
|
||||
button-one-props button-two-props theme scroll?]}
|
||||
(merge default-props props)]
|
||||
[rn/view {:style container-style}
|
||||
[:<>
|
||||
[rn/view {:style style/buttons-container}
|
||||
(when (= actions :2-actions)
|
||||
[button/button
|
||||
@@ -44,8 +43,7 @@
|
||||
:container-style style/button-container
|
||||
:background (when scroll? :blur)
|
||||
:theme theme
|
||||
:accessibility-label :button-one
|
||||
:customization-color customization-color}
|
||||
:accessibility-label :button-one}
|
||||
button-one-props)
|
||||
button-one-label]]
|
||||
(when description
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
(ns quo.components.dropdowns.dropdown-input.view
|
||||
(:require
|
||||
[quo.components.dropdowns.dropdown.properties :as properties]
|
||||
[quo.components.dropdowns.dropdown.style :as style]
|
||||
[quo.components.icon :as icon]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.customization-colors :as customization-colors]
|
||||
[quo.theme :as theme]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn view-internal
|
||||
[]
|
||||
[rn/pressable
|
||||
{:on-press nil}])
|
||||
|
||||
(def view
|
||||
"Props:
|
||||
- :on-press - function to call when pressed"
|
||||
(theme/with-theme view-internal))
|
||||
@@ -65,23 +65,22 @@
|
||||
ens-regex address-regex
|
||||
valid-ens-or-address?]}]
|
||||
(let [on-change (fn [text]
|
||||
(when (not= @value text)
|
||||
(let [ens? (when ens-regex
|
||||
(boolean (re-matches ens-regex text)))
|
||||
address? (when address-regex
|
||||
(boolean (re-matches address-regex text)))]
|
||||
(if (> (count text) 0)
|
||||
(reset! status :typing)
|
||||
(reset! status :active))
|
||||
(reset! value text)
|
||||
(when on-change-text
|
||||
(on-change-text text))
|
||||
(when (and ens? on-detect-ens)
|
||||
(reset! status :loading)
|
||||
(on-detect-ens text))
|
||||
(when (and address? on-detect-address)
|
||||
(reset! status :loading)
|
||||
(on-detect-address text)))))
|
||||
(let [ens? (when ens-regex
|
||||
(boolean (re-matches ens-regex text)))
|
||||
address? (when address-regex
|
||||
(boolean (re-matches address-regex text)))]
|
||||
(if (> (count text) 0)
|
||||
(reset! status :typing)
|
||||
(reset! status :active))
|
||||
(reset! value text)
|
||||
(when on-change-text
|
||||
(on-change-text text))
|
||||
(when (and ens? on-detect-ens)
|
||||
(reset! status :loading)
|
||||
(on-detect-ens text))
|
||||
(when (and address? on-detect-address)
|
||||
(reset! status :loading)
|
||||
(on-detect-address text))))
|
||||
on-paste (fn []
|
||||
(clipboard/get-string
|
||||
(fn [clipboard]
|
||||
@@ -91,8 +90,6 @@
|
||||
on-clear (fn []
|
||||
(reset! value "")
|
||||
(reset! status (if @focused? :active :default))
|
||||
(when on-change-text
|
||||
(on-change-text ""))
|
||||
(when on-clear
|
||||
(on-clear)))
|
||||
on-scan #(when on-scan
|
||||
@@ -122,7 +119,6 @@
|
||||
:auto-complete (when platform/ios? :none)
|
||||
:auto-capitalize :none
|
||||
:auto-correct false
|
||||
:spell-check false
|
||||
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
|
||||
:on-focus on-focus
|
||||
:on-blur on-blur
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
return-key-type
|
||||
size
|
||||
theme
|
||||
on-focus
|
||||
on-blur
|
||||
container-style]
|
||||
:or {max-length 0
|
||||
auto-focus false
|
||||
@@ -50,14 +48,8 @@
|
||||
:accessibility-label :profile-title-input
|
||||
:keyboard-appearance (quo.theme/theme-value :light :dark theme)
|
||||
:return-key-type return-key-type
|
||||
:on-focus (fn []
|
||||
(when (fn? on-focus)
|
||||
(on-focus))
|
||||
(reset! focused? true))
|
||||
:on-blur (fn []
|
||||
(when (fn? on-blur)
|
||||
(on-blur))
|
||||
(reset! focused? false))
|
||||
:on-focus #(swap! focused? (constantly true))
|
||||
:on-blur #(swap! focused? (constantly false))
|
||||
:auto-focus auto-focus
|
||||
:input-mode :text
|
||||
:on-change-text on-change
|
||||
|
||||
@@ -31,5 +31,4 @@
|
||||
:align-items :center})
|
||||
|
||||
(def account-container
|
||||
{:margin-left 8
|
||||
:padding-right 56})
|
||||
{:margin-left 8})
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
(ns quo.components.list-items.address.view
|
||||
(:require
|
||||
[quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
|
||||
[clojure.string :as string]
|
||||
[quo.components.avatars.wallet-user-avatar :as wallet-user-avatar]
|
||||
[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]
|
||||
[reagent.core :as reagent]
|
||||
[utils.address :as address]))
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn- left-container
|
||||
[{:keys [theme address networks blur?]}]
|
||||
@@ -25,8 +25,7 @@
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
:weight :semi-bold
|
||||
:style {:color (colors/resolve-color network theme)}}
|
||||
(str (subs (name network) 0 3) ":")])
|
||||
:style {:color (colors/resolve-color network)}} (str (subs (name network) 0 3) ":")])
|
||||
networks)
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
@@ -34,7 +33,7 @@
|
||||
:style {:color (if blur?
|
||||
colors/white
|
||||
(colors/theme-colors colors/neutral-100 colors/white theme))}}
|
||||
(address/get-shortened-key address)]]]])
|
||||
(string/replace address "x" "×")]]]])
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
@@ -61,7 +60,6 @@
|
||||
[left-container
|
||||
{:theme theme
|
||||
:networks networks
|
||||
:address address
|
||||
:blur? blur?}]]))))
|
||||
:address address}]]))))
|
||||
|
||||
(def view (quo.theme/with-theme internal-view))
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
(when (and members (= type :discover))
|
||||
[community-view/community-stats-column
|
||||
{:type :list-view
|
||||
:theme theme
|
||||
:blur? blur?
|
||||
:members-count (:members-count members)
|
||||
:active-count (:active-count members)}])]
|
||||
[info-component
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
(h/render [saved-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/resolve-color :blue :light 5)})))
|
||||
{:backgroundColor (colors/custom-color :blue 50 5)})))
|
||||
|
||||
(h/test "on-press-in changes state to :pressed with blur? enabled"
|
||||
(h/render [saved-address/view {:blur? true}])
|
||||
@@ -22,25 +22,24 @@
|
||||
{:backgroundColor colors/white-opa-5})))
|
||||
|
||||
(h/test "on-press-out changes state to :active"
|
||||
(h/render [saved-address/view {:active-state? true}])
|
||||
(h/render [saved-address/view])
|
||||
(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/resolve-color :blue :light 10)})))
|
||||
{:backgroundColor (colors/custom-color :blue 50 10)})))
|
||||
|
||||
(h/test "on-press-out changes state to :active with blur? enabled"
|
||||
(h/render [saved-address/view
|
||||
{:blur? true
|
||||
:active-state? true}])
|
||||
(h/render [saved-address/view {: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)
|
||||
{:backgroundColor colors/white-opa-10})))
|
||||
|
||||
(h/test "on-press container calls on-press"
|
||||
(h/test "on-press-out calls on-press"
|
||||
(let [on-press (h/mock-fn)]
|
||||
(h/render [saved-address/view {:on-press on-press}])
|
||||
(h/fire-event :on-press (h/get-by-label-text :container))
|
||||
(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/was-called on-press)))
|
||||
|
||||
(h/test "renders options button if type :action"
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
(ns quo.components.list-items.saved-address.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
|
||||
[quo.components.avatars.wallet-user-avatar :as wallet-user-avatar]
|
||||
[quo.components.icon :as icon]
|
||||
[quo.components.list-items.saved-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]
|
||||
[reagent.core :as reagent]
|
||||
[utils.address :as address]))
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn- left-container
|
||||
[{:keys [blur? theme name ens address customization-color]}]
|
||||
[{:keys [blur? theme name address customization-color]}]
|
||||
(let [names (remove string/blank? (string/split name " "))
|
||||
first-name (if (> (count names) 0) (first names) "")
|
||||
last-name (if (> (count names) 1) (last names) "")]
|
||||
[rn/view {:style style/left-container}
|
||||
[wallet-user-avatar/wallet-user-avatar
|
||||
{:size :medium
|
||||
:f-name first-name
|
||||
:l-name last-name
|
||||
:customization-color customization-color}]
|
||||
{:size :medium
|
||||
:f-name first-name
|
||||
:l-name last-name
|
||||
:color customization-color}]
|
||||
[rn/view {:style style/account-container}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
@@ -33,7 +32,7 @@
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address blur? theme)}
|
||||
(or ens (address/get-shortened-key address))]]]]))
|
||||
address]]]]))
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
@@ -43,39 +42,39 @@
|
||||
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
|
||||
(fn [{:keys [blur? user-props 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)]
|
||||
(let [new-state (if @active? :default :active)]
|
||||
(when @timer (js/clearTimeout @timer))
|
||||
(reset! timer nil)
|
||||
(reset! active? (= new-state :active))
|
||||
(reset! state new-state)))]
|
||||
(reset! state new-state)
|
||||
(when on-press
|
||||
(on-press))))]
|
||||
[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))}]])]))))
|
||||
:customization-color (:customization-color user-props)}]
|
||||
[rn/pressable
|
||||
{:accessibility-label :options-button
|
||||
:on-press #(when on-options-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))
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[utils.address :as address]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn- account
|
||||
@@ -31,7 +30,7 @@
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address theme)}
|
||||
(address/get-shortened-key address)]])
|
||||
address]])
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
|
||||
@@ -27,9 +27,8 @@
|
||||
{:size 20
|
||||
:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]
|
||||
:checkbox
|
||||
[selectors/view
|
||||
{:type :checkbox
|
||||
:checked? checked?
|
||||
[selectors/checkbox
|
||||
{:checked? checked?
|
||||
:accessibility-label :user-list-toggle-check
|
||||
:disabled? disabled?
|
||||
:on-change (when on-check on-check)}]
|
||||
|
||||
@@ -5,22 +5,23 @@
|
||||
(def container
|
||||
{:flex-wrap :nowrap
|
||||
:flex-direction :row
|
||||
:align-items :flex-end})
|
||||
:align-items :center})
|
||||
|
||||
(def name-container
|
||||
{:margin-right 8
|
||||
:flex-direction :row
|
||||
:align-items :flex-end
|
||||
})
|
||||
|
||||
(defn middle-dot
|
||||
(defn middle-dot-nickname
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)
|
||||
:margin-horizontal 2})
|
||||
:margin-horizontal 4})
|
||||
|
||||
(defn chat-key-text
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)})
|
||||
{:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)
|
||||
:margin-left 8
|
||||
:padding-top 1})
|
||||
|
||||
(defn middle-dot-chat-key
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)
|
||||
:margin-left 4})
|
||||
|
||||
(defn primary-name
|
||||
[muted? theme]
|
||||
@@ -33,13 +34,14 @@
|
||||
[theme]
|
||||
{:padding-top 1
|
||||
:flex-shrink 999999
|
||||
:min-width 40
|
||||
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)})
|
||||
|
||||
(defn icon-container
|
||||
[is-first?]
|
||||
{:margin-left (if is-first? 4 2)
|
||||
:margin-bottom 2})
|
||||
(def icon-container
|
||||
{:margin-left 4})
|
||||
|
||||
(defn time-text
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)})
|
||||
{:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)
|
||||
:padding-top 1
|
||||
:margin-left 8})
|
||||
|
||||
@@ -14,48 +14,46 @@
|
||||
muted? size theme]
|
||||
:or {size 13}}]
|
||||
[rn/view {:style (merge style/container style {:height (if (= size 15) 21.75 18.2)})}
|
||||
[rn/view {:style style/name-container}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size (if (= size 15) :paragraph-1 :paragraph-2)
|
||||
:number-of-lines 1
|
||||
:accessibility-label :author-primary-name
|
||||
:style (style/primary-name muted? theme)}
|
||||
primary-name]
|
||||
(when (not (string/blank? secondary-name))
|
||||
[:<>
|
||||
[text/text
|
||||
{:size :label
|
||||
:number-of-lines 1
|
||||
:style (style/middle-dot theme)}
|
||||
middle-dot]
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :label
|
||||
:number-of-lines 1
|
||||
:accessibility-label :author-secondary-name
|
||||
:style (style/secondary-name theme)}
|
||||
secondary-name]])
|
||||
(when contact?
|
||||
[icons/icon :main-icons2/contact
|
||||
{:size 12
|
||||
:no-color true
|
||||
:container-style (style/icon-container true)}])
|
||||
(cond
|
||||
verified?
|
||||
[icons/icon :main-icons2/verified
|
||||
{:size 12
|
||||
:no-color true
|
||||
:container-style (style/icon-container contact?)}]
|
||||
untrustworthy?
|
||||
[icons/icon :main-icons2/untrustworthy
|
||||
{:size 12
|
||||
:no-color true
|
||||
:container-style (style/icon-container contact?)}])
|
||||
]
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size (if (= size 15) :paragraph-1 :paragraph-2)
|
||||
:number-of-lines 1
|
||||
:accessibility-label :author-primary-name
|
||||
:style (style/primary-name muted? theme)}
|
||||
primary-name]
|
||||
(when (not (string/blank? secondary-name))
|
||||
[:<>
|
||||
[text/text
|
||||
{:size :label
|
||||
:number-of-lines 1
|
||||
:style (style/middle-dot-nickname theme)}
|
||||
middle-dot]
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :label
|
||||
:number-of-lines 1
|
||||
:accessibility-label :author-secondary-name
|
||||
:style (style/secondary-name theme)}
|
||||
secondary-name]])
|
||||
(when contact?
|
||||
[icons/icon :main-icons2/contact
|
||||
{:size 12
|
||||
:no-color true
|
||||
:container-style style/icon-container}])
|
||||
(cond
|
||||
verified?
|
||||
[icons/icon :main-icons2/verified
|
||||
{:size 12
|
||||
:no-color true
|
||||
:container-style style/icon-container}]
|
||||
untrustworthy?
|
||||
[icons/icon :main-icons2/untrustworthy
|
||||
{:size 12
|
||||
:no-color true
|
||||
:container-style style/icon-container}])
|
||||
(when (and (not verified?) short-chat-key)
|
||||
[text/text
|
||||
{:weight :monospace
|
||||
{:monospace true
|
||||
:size :label
|
||||
:number-of-lines 1
|
||||
:style (style/chat-key-text theme)}
|
||||
@@ -65,7 +63,7 @@
|
||||
{:monospace true
|
||||
:size :label
|
||||
:number-of-lines 1
|
||||
:style (style/middle-dot theme)}
|
||||
:style (style/middle-dot-chat-key theme)}
|
||||
middle-dot])
|
||||
(when time-str
|
||||
[text/text
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]))
|
||||
|
||||
(def height 56)
|
||||
|
||||
(def root-container
|
||||
{:height height})
|
||||
|
||||
(defn container
|
||||
[state theme]
|
||||
{:padding-top 12
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
(defn- view-internal
|
||||
[{:keys [theme container-style default-active state data on-press active-id]}]
|
||||
[rn/view
|
||||
{:style container-style
|
||||
{:style (merge style/root-container container-style)
|
||||
:accessibility-label :showcase-nav}
|
||||
[rn/flat-list
|
||||
{:data data
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
{:on-press on-change
|
||||
:accessibility-label :disclaimer-touchable-opacity}
|
||||
[rn/view {:style (merge container-style (style/container blur?))}
|
||||
[selectors/view
|
||||
{:type :checkbox
|
||||
:accessibility-label accessibility-label
|
||||
[selectors/checkbox
|
||||
{:accessibility-label accessibility-label
|
||||
:blur? blur?
|
||||
:checked? checked?
|
||||
:on-change on-change}]
|
||||
|
||||
@@ -6,27 +6,27 @@
|
||||
|
||||
(defn render-toggle
|
||||
([]
|
||||
(render-toggle {:type :toggle}))
|
||||
(render-toggle {}))
|
||||
([opts]
|
||||
(h/render (reagent/as-element [selectors/view (assoc opts :type :toggle)]))))
|
||||
(h/render (reagent/as-element [selectors/toggle opts]))))
|
||||
|
||||
(defn render-checkbox
|
||||
([]
|
||||
(render-checkbox {:type :checkbox}))
|
||||
(render-checkbox {}))
|
||||
([opts]
|
||||
(h/render (reagent/as-element [selectors/view (assoc opts :type :checkbox)]))))
|
||||
(h/render (reagent/as-element [selectors/checkbox opts]))))
|
||||
|
||||
(defn render-filled-checkbox
|
||||
(defn render-checkbox-prefill
|
||||
([]
|
||||
(render-filled-checkbox {:type :filled-checkbox}))
|
||||
(render-checkbox-prefill {}))
|
||||
([opts]
|
||||
(h/render (reagent/as-element [selectors/view (assoc opts :type :filled-checkbox)]))))
|
||||
(h/render (reagent/as-element [selectors/checkbox-prefill opts]))))
|
||||
|
||||
(defn render-radio
|
||||
([]
|
||||
(render-radio {:type :radio}))
|
||||
(render-radio {}))
|
||||
([opts]
|
||||
(h/render (reagent/as-element [selectors/view (assoc opts :type :radio)]))))
|
||||
(h/render (reagent/as-element [selectors/radio opts]))))
|
||||
|
||||
(h/test "default render of toggle component"
|
||||
(render-toggle)
|
||||
@@ -58,12 +58,12 @@
|
||||
(h/fire-event :press (h/get-by-test-id "checkbox-component"))
|
||||
(h/was-called mock-fn)))
|
||||
|
||||
(h/test "default render of filled-checkbox component"
|
||||
(render-filled-checkbox)
|
||||
(h/is-truthy (h/get-by-test-id "filled-checkbox-component")))
|
||||
(h/test "default render of checkbox-prefill component"
|
||||
(render-checkbox-prefill)
|
||||
(h/is-truthy (h/get-by-test-id "checkbox-prefill-component")))
|
||||
|
||||
(h/test "filled-checkbox component on change is working"
|
||||
(h/test "checkbox-prefill component on change is working"
|
||||
(let [mock-fn (h/mock-fn)]
|
||||
(render-filled-checkbox {:on-change mock-fn})
|
||||
(h/fire-event :press (h/get-by-test-id "filled-checkbox-component"))
|
||||
(render-checkbox-prefill {:on-change mock-fn})
|
||||
(h/fire-event :press (h/get-by-test-id "checkbox-prefill-component"))
|
||||
(h/was-called mock-fn)))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[customization-color theme]
|
||||
{:normal {:checked (colors/resolve-color customization-color theme)
|
||||
:unchecked (colors/theme-colors colors/neutral-30 colors/neutral-80 theme)}
|
||||
:blur {:checked (colors/theme-colors (colors/resolve-color customization-color theme)
|
||||
:blur {:checked (colors/theme-colors (colors/custom-color customization-color 50)
|
||||
colors/white-opa-70
|
||||
theme)
|
||||
:unchecked (colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-10 theme)}})
|
||||
@@ -15,7 +15,7 @@
|
||||
[customization-color theme]
|
||||
{:normal {:checked (colors/resolve-color customization-color theme)
|
||||
:unchecked (colors/theme-colors colors/neutral-30 colors/neutral-70 theme)}
|
||||
:blur {:checked (colors/theme-colors (colors/resolve-color customization-color theme)
|
||||
:blur {:checked (colors/theme-colors (colors/custom-color customization-color 50)
|
||||
colors/white
|
||||
theme)
|
||||
:unchecked (colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-40 theme)}})
|
||||
@@ -29,7 +29,7 @@
|
||||
[customization-color theme]
|
||||
{:normal {:checked (colors/resolve-color customization-color theme)
|
||||
:unchecked (colors/theme-colors colors/white-opa-40 colors/neutral-80-opa-40 theme)}
|
||||
:blur {:checked (colors/theme-colors (colors/resolve-color customization-color theme)
|
||||
:blur {:checked (colors/theme-colors (colors/custom-color customization-color 50)
|
||||
colors/white)
|
||||
:unchecked colors/white-opa-5}})
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
{:normal (colors/theme-colors colors/neutral-30 colors/neutral-70 theme)
|
||||
:blur (colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-40 theme)})
|
||||
|
||||
(defn- filled-checkbox-background-color
|
||||
(defn- checkbox-prefill-background-color
|
||||
[theme]
|
||||
{:normal (colors/theme-colors colors/neutral-30 colors/neutral-80 theme)
|
||||
:blur (colors/theme-colors colors/neutral-80-opa-10 colors/white-opa-10 theme)})
|
||||
@@ -119,16 +119,16 @@
|
||||
(colors/theme-colors colors/white colors/neutral-100 theme)
|
||||
colors/white)})
|
||||
|
||||
(defn filled-checkbox
|
||||
(defn checkbox-prefill
|
||||
[{:keys [disabled? blur? container-style theme]}]
|
||||
(assoc container-style
|
||||
:height 21
|
||||
:width 21
|
||||
:border-radius 6
|
||||
:opacity (if disabled? 0.3 1)
|
||||
:background-color (get-color (filled-checkbox-background-color theme) blur?)))
|
||||
:background-color (get-color (checkbox-prefill-background-color theme) blur?)))
|
||||
|
||||
(defn filled-checkbox-check
|
||||
(defn checkbox-prefill-check
|
||||
[checked? _blur? theme]
|
||||
{:size 20
|
||||
:color (when checked? (colors/theme-colors colors/neutral-100 colors/white theme))})
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
(when checked-atom (swap! checked-atom not))
|
||||
(when on-change (on-change (not checked?))))
|
||||
|
||||
(defn- base-selector
|
||||
(defn- selector-internal
|
||||
[{:keys [default-checked? checked?]}]
|
||||
(let [controlled-component? (some? checked?)
|
||||
internal-checked? (when-not controlled-component?
|
||||
@@ -28,13 +28,14 @@
|
||||
:container-style container-style
|
||||
:customization-color customization-color
|
||||
:theme theme})]
|
||||
[rn/pressable
|
||||
[rn/touchable-without-feedback
|
||||
(when-not disabled?
|
||||
{:on-press #(handle-press on-change internal-checked? actual-checked?)})
|
||||
[rn/view
|
||||
{:style outer-styles
|
||||
:needs-offscreen-alpha-compositing true
|
||||
:accessibility-label accessibility-label
|
||||
:accessibility-role :checkbox
|
||||
:testID test-id}
|
||||
[rn/view
|
||||
{:style (inner-style-fn {:theme theme
|
||||
@@ -44,49 +45,39 @@
|
||||
(when (and icon-style-fn actual-checked?)
|
||||
[icons/icon :i/check-small (icon-style-fn actual-checked? blur? theme)])]]]))))
|
||||
|
||||
(defn- toggle
|
||||
(def ^:private selector (quo.theme/with-theme selector-internal))
|
||||
|
||||
(defn toggle
|
||||
[props]
|
||||
[base-selector
|
||||
[selector
|
||||
(assoc props
|
||||
:label-prefix "toggle"
|
||||
:outer-style-fn style/toggle
|
||||
:inner-style-fn style/toggle-inner)])
|
||||
|
||||
(defn- radio
|
||||
(defn radio
|
||||
[props]
|
||||
[base-selector
|
||||
[selector
|
||||
(assoc props
|
||||
:label-prefix "radio"
|
||||
:outer-style-fn style/radio
|
||||
:inner-style-fn style/radio-inner)])
|
||||
|
||||
(defn- checkbox
|
||||
(defn checkbox
|
||||
[props]
|
||||
[base-selector
|
||||
[selector
|
||||
(assoc props
|
||||
:label-prefix "checkbox"
|
||||
:outer-style-fn style/checkbox
|
||||
:inner-style-fn style/common-checkbox-inner
|
||||
:icon-style-fn style/checkbox-check)])
|
||||
|
||||
(defn- filled-checkbox
|
||||
(defn checkbox-prefill
|
||||
[props]
|
||||
[base-selector
|
||||
[selector
|
||||
(assoc props
|
||||
:label-prefix "filled-checkbox"
|
||||
:outer-style-fn style/filled-checkbox
|
||||
:label-prefix "checkbox-prefill"
|
||||
:outer-style-fn style/checkbox-prefill
|
||||
:inner-style-fn style/common-checkbox-inner
|
||||
:icon-style-fn style/filled-checkbox-check)])
|
||||
:icon-style-fn style/checkbox-prefill-check)])
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [type]
|
||||
:or {type :toggle}
|
||||
:as props}]
|
||||
(case type
|
||||
:toggle [toggle props]
|
||||
:radio [radio props]
|
||||
:checkbox [checkbox props]
|
||||
:filled-checkbox [filled-checkbox props]
|
||||
nil))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
|
||||
@@ -29,9 +29,8 @@
|
||||
[rn/view {:style style/card-footer-label-container}
|
||||
[text/text {:size :paragraph-2} label]]
|
||||
[rn/view {:style style/card-footer-toggle-container}
|
||||
[selectors/view
|
||||
{:type :toggle
|
||||
:disabled? (not active?)
|
||||
[selectors/toggle
|
||||
{:disabled? (not active?)
|
||||
:on-change on-toggle}]]]])
|
||||
|
||||
(defn- selection-indicator
|
||||
|
||||
@@ -97,7 +97,9 @@
|
||||
{:type :outline
|
||||
:size 24})
|
||||
(:button-text action-props)]
|
||||
:selector [selectors/view action-props]
|
||||
:selector (if (= (:type action-props) :checkbox)
|
||||
[selectors/checkbox action-props]
|
||||
[selectors/toggle action-props])
|
||||
nil)])
|
||||
|
||||
(defn- internal-view
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[quo.components.avatars.account-avatar.view :as account-avatar]
|
||||
[quo.components.avatars.channel-avatar.view :as channel-avatar]
|
||||
[quo.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo.components.avatars.wallet-user-avatar.view :as wallet-avatar]
|
||||
[quo.components.avatars.wallet-user-avatar :as wallet-avatar]
|
||||
[quo.components.share.qr-code.style :as style]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]))
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
(ns quo.components.tags.summary-tag.component-spec
|
||||
(:require
|
||||
[quo.components.tags.summary-tag.view :as summary-tag]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "Summary tag component tests"
|
||||
(h/test "Basic render"
|
||||
(h/render [summary-tag/view])
|
||||
(h/is-truthy (h/query-by-label-text :container)))
|
||||
|
||||
(h/test "Account view render"
|
||||
(h/render [summary-tag/view
|
||||
{:type :account
|
||||
:label "Alice's Account"
|
||||
:customization-color "#ff0000"
|
||||
:emoji "🎉"}])
|
||||
(h/is-truthy (h/get-by-text "Alice's Account")))
|
||||
|
||||
(h/test "Network view render"
|
||||
(h/render [summary-tag/view
|
||||
{:type :network
|
||||
:label "Ethereum Network"
|
||||
:image-source "path/to/ethereum-logo.png"}])
|
||||
(h/is-truthy (h/get-by-text "Ethereum Network")))
|
||||
|
||||
(h/test "Saved address view render"
|
||||
(h/render [summary-tag/view
|
||||
{:type :saved-address
|
||||
:label "Charlie's Wallet"
|
||||
:customization-color "#00ff00"}])
|
||||
(h/is-truthy (h/get-by-text "Charlie's Wallet")))
|
||||
|
||||
(h/test "Collectible view render"
|
||||
(h/render [summary-tag/view
|
||||
{:type :collectible
|
||||
:label "Rare Artifact"
|
||||
:image-source "path/to/artifact-image.png"}])
|
||||
(h/is-truthy (h/get-by-text "Rare Artifact")))
|
||||
|
||||
(h/test "User view render"
|
||||
(h/render [summary-tag/view
|
||||
{:type :user
|
||||
:label "Bob Smith"
|
||||
:image-source "path/to/profile-pic.png"
|
||||
:customization-color "#0000ff"}])
|
||||
(h/is-truthy (h/get-by-text "Bob Smith")))
|
||||
|
||||
(h/test "Token view render"
|
||||
(h/render [summary-tag/view
|
||||
{:type :token
|
||||
:label "1,000 SNT"
|
||||
:image-source "path/to/token-image.png"}])
|
||||
(h/is-truthy (h/get-by-text "1,000 SNT"))))
|
||||
@@ -1,29 +0,0 @@
|
||||
(ns quo.components.tags.summary-tag.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]))
|
||||
|
||||
(defn main
|
||||
[{:keys [type theme customization-color]}]
|
||||
{:justify-content :center
|
||||
:align-items :center
|
||||
:height 32
|
||||
:padding-left 4
|
||||
:padding-right 10
|
||||
:flex-direction :row
|
||||
:border-radius (if (#{:account :collectible} type) 10 16)
|
||||
:background-color (colors/resolve-color customization-color theme 10)})
|
||||
|
||||
(defn label
|
||||
[type theme]
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white theme)
|
||||
:margin-left (if (= type :address) 6 4)})
|
||||
|
||||
(def collectible-image
|
||||
{:width 24
|
||||
:height 24
|
||||
:border-radius 10})
|
||||
|
||||
(def token-image
|
||||
{:width 24
|
||||
:height 24
|
||||
:border-radius 12})
|
||||
@@ -1,67 +0,0 @@
|
||||
(ns quo.components.tags.summary-tag.view
|
||||
(:require
|
||||
[quo.components.avatars.account-avatar.view :as account-avatar]
|
||||
[quo.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.tags.summary-tag.style :as style]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn- left-view
|
||||
[{:keys [label type customization-color emoji image-source]}]
|
||||
(case type
|
||||
:account
|
||||
[account-avatar/view
|
||||
{:customization-color customization-color
|
||||
:size 24
|
||||
:emoji emoji
|
||||
:type :default}]
|
||||
:network
|
||||
[rn/image
|
||||
{:source image-source
|
||||
:style style/token-image}]
|
||||
:saved-address
|
||||
[wallet-user-avatar/wallet-user-avatar
|
||||
{:full-name label
|
||||
:size :size-24
|
||||
:customization-color customization-color}]
|
||||
:collectible
|
||||
[rn/image
|
||||
{:source image-source
|
||||
:style style/collectible-image}]
|
||||
:user
|
||||
[user-avatar/user-avatar
|
||||
{:full-name label
|
||||
:size :xs
|
||||
:profile-picture image-source
|
||||
:customization-color customization-color}]
|
||||
:token
|
||||
[rn/image
|
||||
{:source image-source
|
||||
:style style/token-image}]
|
||||
nil))
|
||||
|
||||
(defn- view-internal
|
||||
"Options:
|
||||
- :label - string - tag label
|
||||
- :customization-color - color - It will be passed down to components that
|
||||
should vary based on a custom color.
|
||||
- :type - :token / :user / :collectible / :saved-address / :network / :account
|
||||
- :emoji - string - emoji used for displaying account avatar
|
||||
- :image-source - resource - image to display on :network, :collectible :user and :token
|
||||
- :theme - :light / :dark"
|
||||
[{:keys [label customization-color type theme]
|
||||
:as props
|
||||
:or {customization-color colors/neutral-80-opa-5}}]
|
||||
[rn/view
|
||||
{:accessibility-label :container
|
||||
:style (style/main (assoc props :customization-color customization-color))}
|
||||
[left-view props]
|
||||
[text/text
|
||||
{:style (style/label type theme)
|
||||
:weight :semi-bold
|
||||
:size :heading-1} label]])
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
@@ -1,6 +1,5 @@
|
||||
(ns quo.components.text-combinations.view
|
||||
(:require
|
||||
[quo.components.buttons.button.view :as button]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.text-combinations.style :as style]
|
||||
[quo.theme :as theme]
|
||||
@@ -24,31 +23,20 @@
|
||||
avatar
|
||||
title-accessibility-label
|
||||
description
|
||||
description-accessibility-label
|
||||
button-icon
|
||||
button-on-press
|
||||
customization-color]}]
|
||||
description-accessibility-label]}]
|
||||
[rn/view {:style container-style}
|
||||
[rn/view
|
||||
{:style {:flex-direction :row
|
||||
:justify-content :space-between}}
|
||||
[rn/view {:style style/title-container}
|
||||
(when avatar
|
||||
[rn/view {:style style/avatar-container}
|
||||
[icon avatar 32]])
|
||||
[text/text
|
||||
{:accessibility-label title-accessibility-label
|
||||
:weight :semi-bold
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines 1
|
||||
:size :heading-1}
|
||||
title]]
|
||||
(when button-icon
|
||||
[button/button
|
||||
{:icon-only? true
|
||||
:on-press button-on-press
|
||||
:customization-color customization-color
|
||||
:size 32} button-icon])]
|
||||
[rn/view {:style style/title-container}
|
||||
(when avatar
|
||||
[rn/view {:style style/avatar-container}
|
||||
[icon avatar 32]])
|
||||
|
||||
[text/text
|
||||
{:accessibility-label title-accessibility-label
|
||||
:weight :semi-bold
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines 1
|
||||
:size :heading-1}
|
||||
title]]
|
||||
(when description
|
||||
[text/text
|
||||
{:accessibility-label description-accessibility-label
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
(defn- colored-network-text
|
||||
[theme network]
|
||||
(let [{:keys [network-name short-name]} network]
|
||||
(let [{:keys [name short-name]} network]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style {:color (colors/resolve-color network-name theme)}}
|
||||
:style {:color (colors/resolve-color name theme)}}
|
||||
(str short-name ":")]))
|
||||
|
||||
(defn- view-internal
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
(defn container
|
||||
[{:keys [blur? customization-color theme selected?]}]
|
||||
{:border-radius 16
|
||||
{:flex 1
|
||||
:border-radius 20
|
||||
:border-width 1
|
||||
:border-color (if selected?
|
||||
(if blur?
|
||||
@@ -27,4 +28,5 @@
|
||||
(def title-container
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :space-between})
|
||||
:justify-content :space-between
|
||||
:flex 1})
|
||||
|
||||
@@ -27,16 +27,13 @@
|
||||
(defn avatar
|
||||
[{{:keys [full-name]} :details
|
||||
avatar-type :type
|
||||
customization-color :customization-color
|
||||
profile-picture :profile-picture}]
|
||||
customization-color :customization-color}]
|
||||
(if (= avatar-type :default-keypair)
|
||||
[user-avatar/user-avatar
|
||||
{:full-name full-name
|
||||
:ring? true
|
||||
:size :small
|
||||
:status-indicator? false
|
||||
:customization-color customization-color
|
||||
:profile-picture profile-picture}]
|
||||
:customization-color customization-color}]
|
||||
[icon-avatar/icon-avatar
|
||||
{:size :size-32
|
||||
:icon :i/placeholder
|
||||
@@ -51,9 +48,8 @@
|
||||
[text/text {:weight :semi-bold}
|
||||
(if (= type :default-keypair) (keypair-string full-name) full-name)]
|
||||
(if (= action :selector)
|
||||
[selectors/view
|
||||
{:type :radio
|
||||
:checked? selected?
|
||||
[selectors/radio
|
||||
{:checked? selected?
|
||||
:blur? blur?
|
||||
:customization-color customization-color}]
|
||||
[rn/pressable {:on-press on-options-press}
|
||||
@@ -87,9 +83,9 @@
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [selected? (reagent/atom true)]
|
||||
(fn [{:keys [accounts action container-style] :as props}]
|
||||
(fn [{:keys [accounts action] :as props}]
|
||||
[rn/pressable
|
||||
{:style (merge (style/container (merge props {:selected? @selected?})) container-style)
|
||||
{:style (style/container (merge props {:selected? @selected?}))
|
||||
:on-press #(when (= action :selector) (reset! selected? (not @selected?)))}
|
||||
[rn/view {:style style/header-container}
|
||||
[avatar props]
|
||||
|
||||
+9
-9
@@ -1,14 +1,13 @@
|
||||
(ns quo.core
|
||||
(:refer-clojure :exclude [filter])
|
||||
(:require
|
||||
quo.components.animated-header-flatlist.view
|
||||
quo.components.avatars.account-avatar.view
|
||||
quo.components.avatars.channel-avatar.view
|
||||
quo.components.avatars.collection-avatar.view
|
||||
quo.components.avatars.group-avatar.view
|
||||
quo.components.avatars.icon-avatar
|
||||
quo.components.avatars.user-avatar.view
|
||||
quo.components.avatars.wallet-user-avatar.view
|
||||
quo.components.avatars.wallet-user-avatar
|
||||
quo.components.banners.banner.view
|
||||
quo.components.browser.browser-input.view
|
||||
quo.components.buttons.button.view
|
||||
@@ -31,7 +30,6 @@
|
||||
quo.components.community.channel-actions
|
||||
quo.components.community.community-card-view
|
||||
quo.components.community.community-list-view
|
||||
quo.components.community.community-stat.view
|
||||
quo.components.community.community-view
|
||||
quo.components.community.icon
|
||||
quo.components.community.token-gating
|
||||
@@ -50,6 +48,7 @@
|
||||
quo.components.drawers.permission-context.view
|
||||
quo.components.dropdowns.dropdown.view
|
||||
quo.components.dropdowns.network-dropdown.view
|
||||
quo.components.dropdowns.dropdown-input.view
|
||||
quo.components.empty-state.empty-state.view
|
||||
quo.components.gradient.gradient-cover.view
|
||||
[quo.components.graph.interactive-graph.view :as interactive-graph]
|
||||
@@ -131,7 +130,6 @@
|
||||
quo.components.tags.number-tag.view
|
||||
quo.components.tags.permission-tag
|
||||
quo.components.tags.status-tags
|
||||
quo.components.tags.summary-tag.view
|
||||
quo.components.tags.tag
|
||||
quo.components.tags.tags
|
||||
quo.components.tags.tiny-tag.view
|
||||
@@ -162,7 +160,7 @@
|
||||
(def group-avatar quo.components.avatars.group-avatar.view/view)
|
||||
(def icon-avatar quo.components.avatars.icon-avatar/icon-avatar)
|
||||
(def user-avatar quo.components.avatars.user-avatar.view/user-avatar)
|
||||
(def wallet-user-avatar quo.components.avatars.wallet-user-avatar.view/wallet-user-avatar)
|
||||
(def wallet-user-avatar quo.components.avatars.wallet-user-avatar/wallet-user-avatar)
|
||||
|
||||
;;;; Banner
|
||||
(def banner quo.components.banners.banner.view/view)
|
||||
@@ -201,7 +199,7 @@
|
||||
(def communities-membership-list-item
|
||||
quo.components.community.community-list-view/communities-membership-list-item)
|
||||
(def community-stats-column quo.components.community.community-view/community-stats-column)
|
||||
(def community-stat quo.components.community.community-stat.view/view)
|
||||
(def community-stats quo.components.community.community-view/community-stats)
|
||||
(def community-tags quo.components.community.community-view/community-tags)
|
||||
(def community-title quo.components.community.community-view/community-title)
|
||||
(def permission-tag-container quo.components.community.community-view/permission-tag-container)
|
||||
@@ -232,6 +230,7 @@
|
||||
;;;; Dropdowns
|
||||
(def dropdown quo.components.dropdowns.dropdown.view/view)
|
||||
(def network-dropdown quo.components.dropdowns.network-dropdown.view/view)
|
||||
(def dropdown-input quo.components.dropdowns.dropdown-input.view/view)
|
||||
|
||||
;;;; Empty State
|
||||
(def empty-state quo.components.empty-state.empty-state.view/empty-state)
|
||||
@@ -242,7 +241,6 @@
|
||||
|
||||
;;;; Header
|
||||
(def header quo.components.header/header)
|
||||
(def animated-header-list quo.components.animated-header-flatlist.view/animated-header-list)
|
||||
|
||||
;;;; Gradient
|
||||
(def gradient-cover quo.components.gradient.gradient-cover.view/view)
|
||||
@@ -332,7 +330,10 @@
|
||||
(def reactions-selector quo.components.selectors.reactions-selector.view/view)
|
||||
(def react quo.components.selectors.react.view/view)
|
||||
(def react-selector quo.components.selectors.react-selector.view/view)
|
||||
(def selectors quo.components.selectors.selectors.view/view)
|
||||
(def checkbox quo.components.selectors.selectors.view/checkbox)
|
||||
(def toggle quo.components.selectors.selectors.view/toggle)
|
||||
(def radio quo.components.selectors.selectors.view/radio)
|
||||
(def checkbox-prefill quo.components.selectors.selectors.view/checkbox-prefill)
|
||||
|
||||
;;;; Settings
|
||||
(def account quo.components.settings.accounts.view/account)
|
||||
@@ -361,7 +362,6 @@
|
||||
(def number-tag quo.components.tags.number-tag.view/view)
|
||||
(def permission-tag quo.components.tags.permission-tag/tag)
|
||||
(def status-tag quo.components.tags.status-tags/status-tag)
|
||||
(def summary-tag quo.components.tags.summary-tag.view/view)
|
||||
(def tag quo.components.tags.tag/tag)
|
||||
(def tags quo.components.tags.tags/tags)
|
||||
(def tiny-tag quo.components.tags.tiny-tag.view/view)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
[quo.components.calendar.calendar.component-spec]
|
||||
[quo.components.calendar.calendar.month-picker.component-spec]
|
||||
[quo.components.colors.color-picker.component-spec]
|
||||
[quo.components.community.community-stat.component-spec]
|
||||
[quo.components.counter.counter.component-spec]
|
||||
[quo.components.counter.step.component-spec]
|
||||
[quo.components.dividers.divider-label.component-spec]
|
||||
@@ -73,7 +72,6 @@
|
||||
[quo.components.switchers.group-messaging-card.component-spec]
|
||||
[quo.components.tags.network-tags.component-spec]
|
||||
[quo.components.tags.status-tags-component-spec]
|
||||
[quo.components.tags.summary-tag.component-spec]
|
||||
[quo.components.tags.tiny-tag.component-spec]
|
||||
[quo.components.wallet.account-card.component-spec]
|
||||
[quo.components.wallet.account-origin.component-spec]
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
[quo.theme :as theme]
|
||||
[react-native.platform :as platform]))
|
||||
|
||||
(def account-colors
|
||||
[:blue :yellow :purple :turquoise :magenta :sky :orange :army :flamingo :camel :copper])
|
||||
|
||||
(defn alpha
|
||||
[value opacity]
|
||||
(when value
|
||||
|
||||
@@ -29,6 +29,3 @@
|
||||
(testing
|
||||
"when using opacity theme is ignored and uses the light suffix resolver"
|
||||
(is (colors/resolve-color :blue :light 10) (colors/resolve-color :blue :dark 10))))
|
||||
|
||||
(deftest test-account-colors-customization
|
||||
(is (every? #(contains? colors/customization %) colors/account-colors)))
|
||||
|
||||
@@ -18,14 +18,12 @@
|
||||
(log/error e))))
|
||||
|
||||
(defn set-item!
|
||||
([k value] (set-item! k value identity))
|
||||
([k value cb]
|
||||
(-> ^js async-storage
|
||||
(.setItem (str k)
|
||||
(clj->transit value))
|
||||
(.then (fn [_] (cb)))
|
||||
(.catch (fn [error]
|
||||
(log/error "[async-storage]" error))))))
|
||||
[k value]
|
||||
(-> ^js async-storage
|
||||
(.setItem (str k)
|
||||
(clj->transit value))
|
||||
(.catch (fn [error]
|
||||
(log/error "[async-storage]" error)))))
|
||||
|
||||
(defn set-item-factory
|
||||
[]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns react-native.react-native-intersection-observer
|
||||
(:require
|
||||
["react-native-intersection-observer" :refer [InView IOFlatList]]
|
||||
[react-native.flat-list :refer [base-list-props]]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def view (reagent/adapt-react-class InView))
|
||||
(def flat-list-comp (reagent/adapt-react-class IOFlatList))
|
||||
|
||||
(defn flat-list
|
||||
[props]
|
||||
[flat-list-comp (base-list-props props)])
|
||||
@@ -16,17 +16,16 @@
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.utils.deprecated-types :as types]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.utils.universal-links.utils :as links]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.address :as address]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.ens.core :as utils.ens]
|
||||
[utils.ethereum.chain :as chain]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.universal-links :as links]
|
||||
[utils.url :as url]))
|
||||
|
||||
(rf/defn update-browser-option
|
||||
@@ -91,7 +90,7 @@
|
||||
(when (not error?)
|
||||
(let [current-url (get-current-url (get-current-browser db))
|
||||
host (url/url-host current-url)]
|
||||
(if (and (not resolved-url) (utils.ens/is-valid-eth-name? host))
|
||||
(if (and (not resolved-url) (ens/is-valid-eth-name? host))
|
||||
{:db (update db :browser/options assoc :resolving? true)
|
||||
:browser/resolve-ens-contenthash {:chain-id (chain/chain-id db)
|
||||
:ens-name host
|
||||
|
||||
@@ -107,15 +107,14 @@
|
||||
[{:keys [db] :as cofx} message]
|
||||
(let [current-chat-id (:current-chat-id db)
|
||||
text (get-in message [:content :text])]
|
||||
{:db (-> db
|
||||
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message]
|
||||
message)
|
||||
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil)
|
||||
(update-in [:chat/inputs current-chat-id :metadata]
|
||||
dissoc
|
||||
:sending-image))
|
||||
:dispatch-n [[:chat.ui/set-chat-input-text nil current-chat-id]
|
||||
[:mention/to-input-field text current-chat-id]]}))
|
||||
{:db (-> db
|
||||
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message]
|
||||
message)
|
||||
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil)
|
||||
(update-in [:chat/inputs current-chat-id :metadata]
|
||||
dissoc
|
||||
:sending-image))
|
||||
:dispatch [:mention/to-input-field text current-chat-id]}))
|
||||
|
||||
(rf/defn show-contact-request-input
|
||||
"Sets reference to previous chat message and focuses on input"
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
[status-im.bottom-sheet.events :as bottom-sheet]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.utils.deprecated-types :as types]
|
||||
[status-im.utils.universal-links.core :as universal-links]
|
||||
[status-im2.common.muting.helpers :refer [format-mute-till]]
|
||||
[status-im2.common.toasts.events :as toasts]
|
||||
[status-im2.common.universal-links :as universal-links]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.chat.events :as chat.events]
|
||||
[status-im2.contexts.shell.activity-center.events :as activity-center]
|
||||
@@ -94,17 +94,9 @@
|
||||
|
||||
(defn- handle-my-request
|
||||
[db {:keys [community-id state deleted] :as request}]
|
||||
(let [{:keys [name]} (get-in db [:communities community-id])]
|
||||
(cond (and (= constants/community-request-to-join-state-pending state) (not deleted))
|
||||
(assoc-in db [:communities/my-pending-requests-to-join community-id] request)
|
||||
(and (= constants/community-request-to-join-state-accepted state) (not deleted))
|
||||
(do (re-frame/dispatch [:toasts/upsert
|
||||
{:icon :i/correct
|
||||
:id :joined-community
|
||||
:icon-color (:positive-01 @colors/theme)
|
||||
:text (i18n/label :t/joined-community {:community name})}])
|
||||
(update-in db [:communities/my-pending-requests-to-join] dissoc community-id))
|
||||
:else (update-in db [:communities/my-pending-requests-to-join] dissoc community-id))))
|
||||
(if (and (= constants/community-request-to-join-state-pending state) (not deleted))
|
||||
(assoc-in db [:communities/my-pending-requests-to-join community-id] request)
|
||||
(update-in db [:communities/my-pending-requests-to-join] dissoc community-id)))
|
||||
|
||||
(defn handle-admin-request
|
||||
[db {:keys [id community-id deleted] :as request}]
|
||||
@@ -228,6 +220,32 @@
|
||||
(log/error "failed to import community" %)
|
||||
(re-frame/dispatch [::failed-to-import %]))}]})
|
||||
|
||||
(rf/defn request-to-join
|
||||
{:events [:communities/request-to-join]}
|
||||
[_ community-id]
|
||||
{:json-rpc/call [{:method "wakuext_requestToJoinCommunity"
|
||||
:params [{:communityId community-id}]
|
||||
:js-response true
|
||||
:on-success #(re-frame/dispatch [:communities/requested-to-join %])
|
||||
:on-error #(log/error "failed to request to join community" community-id)}]})
|
||||
|
||||
(rf/defn requested-to-join-with-password-error
|
||||
{:events [:communities/requested-to-join-with-password-error]}
|
||||
[{:keys [db]} error]
|
||||
{:db (assoc-in db [:password-authentication :error] error)})
|
||||
|
||||
(rf/defn request-to-join-with-password
|
||||
{:events [:communities/request-to-join-with-password]}
|
||||
[_ community-id password]
|
||||
{:json-rpc/call [{:method "wakuext_requestToJoinCommunity"
|
||||
:params [{:communityId community-id :password password}]
|
||||
:js-response true
|
||||
:on-success #(re-frame/dispatch [:communities/requested-to-join %])
|
||||
:on-error (fn [error]
|
||||
(log/error "failed to request to join community" community-id error)
|
||||
(re-frame/dispatch [:communities/requested-to-join-with-password-error
|
||||
error]))}]})
|
||||
|
||||
(rf/defn get-user-requests-to-join
|
||||
{:events [:communities/get-user-requests-to-join]}
|
||||
[_]
|
||||
|
||||
@@ -11,3 +11,9 @@
|
||||
:group/selected-contacts #{}
|
||||
:new-chat-name "")}
|
||||
(navigation/navigate-to :contact-toggle-list nil)))
|
||||
|
||||
(defn displayed-photo
|
||||
[{:keys [images]}]
|
||||
(or (:large images)
|
||||
(:thumbnail images)
|
||||
(first images)))
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.address :as address]))
|
||||
|
||||
(defn public-key->new-contact
|
||||
[public-key]
|
||||
(let [alias (gfycat/generate-gfy public-key)]
|
||||
{:alias alias
|
||||
:name alias
|
||||
:primary-name alias
|
||||
:public-key public-key}))
|
||||
|
||||
(defn public-key-and-ens-name->new-contact
|
||||
[public-key ens-name]
|
||||
(let [contact {:public-key public-key}]
|
||||
(let [contact (public-key->new-contact public-key)]
|
||||
(if ens-name
|
||||
(-> contact
|
||||
(assoc :ens-name ens-name)
|
||||
@@ -18,7 +27,8 @@
|
||||
(defn public-key->contact
|
||||
[contacts public-key]
|
||||
(when public-key
|
||||
(get contacts public-key {:public-key public-key})))
|
||||
(or (get contacts public-key)
|
||||
(public-key->new-contact public-key))))
|
||||
|
||||
(defn- contact-by-address
|
||||
[[addr contact] address]
|
||||
@@ -56,13 +66,8 @@
|
||||
(assoc public-key current-contact))]
|
||||
(->> members
|
||||
(map #(or (get all-contacts %)
|
||||
{:public-key %}))
|
||||
(sort-by (comp string/lower-case
|
||||
(fn [{:keys [primary-name name alias public-key]}]
|
||||
(or primary-name
|
||||
name
|
||||
alias
|
||||
public-key))))
|
||||
(public-key->new-contact %)))
|
||||
(sort-by (comp string/lower-case #(or (:primary-name %) (:name %) (:alias %))))
|
||||
(map #(if (get admins (:public-key %))
|
||||
(assoc % :admin? true)
|
||||
%)))))
|
||||
|
||||
@@ -1,49 +1,55 @@
|
||||
(ns status-im.contact.db-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.contact.db :as contact.db]))
|
||||
(:require
|
||||
[cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.contact.db :as contact.db]
|
||||
[status-im.utils.gfycat.core :as gfycat]))
|
||||
|
||||
(deftest contacts-subs
|
||||
(testing "get-all-contacts-in-group-chat"
|
||||
(let
|
||||
[chat-contact-ids
|
||||
#{"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"
|
||||
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"
|
||||
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}
|
||||
admins
|
||||
#{"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"}
|
||||
(with-redefs [gfycat/generate-gfy (constantly "generated")]
|
||||
(let
|
||||
[chat-contact-ids
|
||||
#{"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"
|
||||
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"
|
||||
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}
|
||||
admins
|
||||
#{"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"}
|
||||
|
||||
contacts
|
||||
{"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"
|
||||
{:last-updated 0
|
||||
:name "User B"
|
||||
:primary-name "User B"
|
||||
:last-online 0
|
||||
:public-key
|
||||
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"}}
|
||||
current-multiaccount
|
||||
{:last-updated 0
|
||||
:signed-up? true
|
||||
:sharing-usage-data? false
|
||||
:primary-name "User A"
|
||||
:name "User A"
|
||||
:public-key
|
||||
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}]
|
||||
(is
|
||||
(=
|
||||
(contact.db/get-all-contacts-in-group-chat chat-contact-ids
|
||||
admins
|
||||
contacts
|
||||
current-multiaccount)
|
||||
[{:admin? true
|
||||
:public-key
|
||||
"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"}
|
||||
{:alias "User A"
|
||||
:primary-name "User A"
|
||||
:public-key
|
||||
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}
|
||||
contacts
|
||||
{"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"
|
||||
{:last-updated 0
|
||||
:name "User B"
|
||||
:primary-name "User B"
|
||||
:last-online 0
|
||||
:public-key
|
||||
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"}}
|
||||
current-multiaccount
|
||||
{:last-updated 0
|
||||
:name "User B"
|
||||
:primary-name "User B"
|
||||
:last-online 0
|
||||
:signed-up? true
|
||||
:sharing-usage-data? false
|
||||
:primary-name "User A"
|
||||
:name "User A"
|
||||
:public-key
|
||||
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"}])))))
|
||||
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}]
|
||||
(is
|
||||
(=
|
||||
(contact.db/get-all-contacts-in-group-chat chat-contact-ids
|
||||
admins
|
||||
contacts
|
||||
current-multiaccount)
|
||||
[{:name "generated"
|
||||
:primary-name "generated"
|
||||
:alias "generated"
|
||||
:admin? true
|
||||
:public-key
|
||||
"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"}
|
||||
{:alias "User A"
|
||||
:primary-name "User A"
|
||||
:public-key
|
||||
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}
|
||||
{:last-updated 0
|
||||
:name "User B"
|
||||
:primary-name "User B"
|
||||
:last-online 0
|
||||
:public-key
|
||||
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"}]))))))
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
(ns status-im.data-store.communities
|
||||
(:require [clojure.set :as set]))
|
||||
|
||||
(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?})})))
|
||||
@@ -1,23 +0,0 @@
|
||||
(ns status-im.data-store.communities-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest is]]
|
||||
[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))))
|
||||
@@ -6,6 +6,7 @@
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.bottom-sheet.events :as bottom-sheet]
|
||||
[status-im.ethereum.ens :as ens]
|
||||
[status-im.ethereum.stateofus :as stateofus]
|
||||
[status-im.multiaccounts.update.core :as multiaccounts.update]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.wallet.utils :as wallet.utils]
|
||||
@@ -13,8 +14,6 @@
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.ens.core :as utils.ens]
|
||||
[utils.ens.stateofus :as stateofus]
|
||||
[utils.ethereum.chain :as chain]
|
||||
[utils.ethereum.eip.eip55 :as eip55]
|
||||
[utils.re-frame :as rf]))
|
||||
@@ -159,7 +158,7 @@
|
||||
(cond
|
||||
|
||||
;; No address for a stateofus subdomain: it can be registered
|
||||
(and (= response utils.ens/default-address) (not custom-domain?))
|
||||
(and (= response ens/default-address) (not custom-domain?))
|
||||
(re-frame/dispatch [::name-resolved username :available])
|
||||
|
||||
;; if we get an address back, we try to get the public key associated
|
||||
@@ -206,7 +205,7 @@
|
||||
|
||||
(defn- valid-custom-domain?
|
||||
[username]
|
||||
(and (utils.ens/is-valid-eth-name? username)
|
||||
(and (ens/is-valid-eth-name? username)
|
||||
(stateofus/lower-case? username)))
|
||||
|
||||
(defn- valid-username?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns utils.ethereum.eip.eip681
|
||||
(ns status-im.ethereum.eip681
|
||||
"Utility function related to [EIP681](https://eips.ethereum.org/EIPS/eip-681)
|
||||
|
||||
This EIP standardize how ethereum payment request can be represented as URI (say to embed them in a QR code).
|
||||
@@ -6,9 +6,11 @@
|
||||
e.g. ethereum:0x1234@1/transfer?to=0x5678&value=1e18&gas=5000"
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[status-im.ethereum.ens :as ens]
|
||||
[status-im.ethereum.tokens :as tokens]
|
||||
[utils.address :as address]
|
||||
[utils.ens.core :as utils.ens]
|
||||
[utils.ethereum.chain :as chain]))
|
||||
[utils.ethereum.chain :as chain]
|
||||
[utils.money :as money]))
|
||||
|
||||
(def scheme "ethereum")
|
||||
(def scheme-separator ":")
|
||||
@@ -18,6 +20,7 @@
|
||||
(def parameter-separator "&")
|
||||
(def key-value-separator "=")
|
||||
|
||||
;;TODO(goranjovic) - rewrite all of these with something more readable than regex
|
||||
(def uri-pattern
|
||||
(re-pattern (str scheme scheme-separator "([^" query-separator "]*)(?:\\" query-separator "(.*))?")))
|
||||
(def authority-path-pattern
|
||||
@@ -68,10 +71,10 @@
|
||||
(let [[_ authority-path query] (re-find uri-pattern s)]
|
||||
(when authority-path
|
||||
(let [[_ raw-address chain-id function-name] (re-find authority-path-pattern authority-path)]
|
||||
(when (or (or (utils.ens/is-valid-eth-name? raw-address) (address/address? raw-address))
|
||||
(when (or (or (ens/is-valid-eth-name? raw-address) (address/address? raw-address))
|
||||
(when (string/starts-with? raw-address "pay-")
|
||||
(let [pay-address (string/replace-first raw-address "pay-" "")]
|
||||
(or (utils.ens/is-valid-eth-name? pay-address)
|
||||
(or (ens/is-valid-eth-name? pay-address)
|
||||
(address/address? pay-address)))))
|
||||
(let [address (if (string/starts-with? raw-address "pay-")
|
||||
(string/replace-first raw-address "pay-" "")
|
||||
@@ -79,7 +82,7 @@
|
||||
(when-let [arguments (parse-arguments function-name query)]
|
||||
(let [contract-address (get-in arguments [:function-arguments :address])]
|
||||
(if-not (or (not contract-address)
|
||||
(or (utils.ens/is-valid-eth-name? contract-address)
|
||||
(or (ens/is-valid-eth-name? contract-address)
|
||||
(address/address? contract-address)))
|
||||
nil
|
||||
(merge {:address address
|
||||
@@ -88,6 +91,31 @@
|
||||
(chain/chain-keyword->chain-id :mainnet))}
|
||||
arguments))))))))))))
|
||||
|
||||
(defn parse-eth-value
|
||||
"Takes a map as returned by `parse-uri` and returns value as BigNumber"
|
||||
[s]
|
||||
(when (string? s)
|
||||
(let [eth? (string/ends-with? s "ETH")
|
||||
^js n (money/bignumber (string/replace s "ETH" ""))]
|
||||
(if eth? (.times n 1e18) n))))
|
||||
|
||||
(defn extract-request-details
|
||||
"Return a map encapsulating request details (with keys `value`, `address` and `symbol`) from a parsed URI.
|
||||
Supports ethereum and erc20 token."
|
||||
[{:keys [value address function-name function-arguments] :as details} all-tokens]
|
||||
(when address
|
||||
(merge details
|
||||
(case function-name
|
||||
nil
|
||||
{:value (parse-eth-value value)
|
||||
:symbol :ETH
|
||||
:address address}
|
||||
"transfer"
|
||||
{:value (money/bignumber (:uint256 function-arguments))
|
||||
:symbol (:symbol (tokens/address->token all-tokens address))
|
||||
:address (:address function-arguments)}
|
||||
nil))))
|
||||
|
||||
(defn- generate-query-string
|
||||
[m]
|
||||
(string/join parameter-separator
|
||||
@@ -114,3 +142,12 @@
|
||||
(let [native-parameters (dissoc parameters :function-name :function-arguments)]
|
||||
(generate-query-string (merge function-arguments native-parameters))))
|
||||
(str query-separator (generate-query-string parameters))))))))
|
||||
|
||||
(defn generate-erc20-uri
|
||||
"Generate a EIP 681 URI encapsulating ERC20 token transfer"
|
||||
[address {sym :symbol value :value :as m} all-tokens]
|
||||
(when-let [token (tokens/symbol->token all-tokens sym)]
|
||||
(generate-uri (:address token)
|
||||
(merge (dissoc m :value :symbol)
|
||||
{:function-name "transfer"
|
||||
:function-arguments {:uint256 value :address address}}))))
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns utils.ethereum.eip.eip681-test
|
||||
(ns status-im.ethereum.eip681-test
|
||||
(:require
|
||||
[cljs.test :refer-macros [deftest is]]
|
||||
[utils.ethereum.eip.eip681 :as eip681]
|
||||
[status-im.ethereum.eip681 :as eip681]
|
||||
[utils.money :as money]))
|
||||
|
||||
(deftest parse-uri
|
||||
@@ -118,6 +118,27 @@
|
||||
:symbol :STT
|
||||
:decimals 18}}})
|
||||
|
||||
(deftest generate-erc20-uri
|
||||
(is (= nil (eip681/generate-erc20-uri nil nil all-tokens)))
|
||||
(is
|
||||
(=
|
||||
"ethereum:0x744d70fdbe2ba4cf95131626614a1763df805b9e/transfer?uint256=5&address=0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
(eip681/generate-erc20-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
{:symbol :SNT :value 5}
|
||||
(:mainnet all-tokens))))
|
||||
(is
|
||||
(=
|
||||
"ethereum:0x744d70fdbe2ba4cf95131626614a1763df805b9e/transfer?uint256=5&address=0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7&gas=10000&gasPrice=10000"
|
||||
(eip681/generate-erc20-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
{:symbol :SNT :value 5 :gas 10000 :gasPrice 10000}
|
||||
(:mainnet all-tokens))))
|
||||
(is
|
||||
(=
|
||||
"ethereum:0x744d70fdbe2ba4cf95131626614a1763df805b9e/transfer?uint256=5&address=0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
(eip681/generate-erc20-uri "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
{:symbol :SNT :chain-id 1 :value 5}
|
||||
(:mainnet all-tokens)))))
|
||||
|
||||
(deftest generate-uri
|
||||
(is (= nil (eip681/generate-uri nil nil)))
|
||||
(is (= "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
@@ -157,3 +178,46 @@
|
||||
"ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7@3/transfer?uint256=5&address=0xc55cF4B03948D7EBc8b9E8BAD92643703811d162"
|
||||
{:keys [address] :as params} (eip681/parse-uri uri)]
|
||||
(is (= uri (eip681/generate-uri address (dissoc params :address))))))
|
||||
|
||||
(deftest parse-eth-value
|
||||
(is (= nil (eip681/parse-eth-value nil)))
|
||||
(is (= nil (eip681/parse-eth-value 1)))
|
||||
(is (= nil (eip681/parse-eth-value "NOT_NUMBER")))
|
||||
(is (.equals (money/bignumber 1) (eip681/parse-eth-value "1")))
|
||||
(is (.equals (money/bignumber 2.014e18) (eip681/parse-eth-value "2.014e18")))
|
||||
(is (.equals (money/bignumber 1e18) (eip681/parse-eth-value "1ETH")))
|
||||
(is (.equals (money/bignumber -1e18) (eip681/parse-eth-value "-1e18")))
|
||||
(is (.equals (money/bignumber 1e18) (eip681/parse-eth-value "1E18")))
|
||||
(is (.equals (money/bignumber "111122223333441239") (eip681/parse-eth-value "111122223333441239"))))
|
||||
|
||||
(deftest extract-request-details
|
||||
(let [{value :value
|
||||
sym :symbol
|
||||
address :address}
|
||||
(eip681/extract-request-details
|
||||
{:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
:value "1ETH"}
|
||||
{})]
|
||||
(is (.equals (money/ether->wei (money/bignumber 1)) value))
|
||||
(is (= :ETH sym))
|
||||
(is (= "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" address)))
|
||||
(is (= (eip681/extract-request-details
|
||||
{:address "0x744d70fdbe2ba4cf95131626614a1763df805b9e" :chain-id 1 :function-name "unknown"}
|
||||
{})
|
||||
{:address "0x744d70fdbe2ba4cf95131626614a1763df805b9e" :chain-id 1 :function-name "unknown"}))
|
||||
(let [{value :value
|
||||
sym :symbol
|
||||
address :address}
|
||||
(eip681/extract-request-details
|
||||
{:address "0x744d70fdbe2ba4cf95131626614a1763df805b9e"
|
||||
:chain-id 1
|
||||
:function-name "transfer"
|
||||
:function-arguments {:uint256 1000 :address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}}
|
||||
{"0x744d70fdbe2ba4cf95131626614a1763df805b9e" {:address
|
||||
"0x744d70fdbe2ba4cf95131626614a1763df805b9e"
|
||||
:name "Status Network Token"
|
||||
:symbol :SNT
|
||||
:decimals 18}})]
|
||||
(is (.equals (money/bignumber 1000) value))
|
||||
(is (= :SNT sym))
|
||||
(is (= "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" address))))
|
||||
@@ -1,12 +1,35 @@
|
||||
(ns status-im.ethereum.ens
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[status-im2.common.json-rpc.events :as json-rpc]
|
||||
[utils.ens.core :as utils.ens]))
|
||||
[status-im2.common.json-rpc.events :as json-rpc]))
|
||||
|
||||
;; this is the addresses of ens registries for the different networks
|
||||
(def ens-registries
|
||||
{:mainnet "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
|
||||
:goerli "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"})
|
||||
|
||||
(def default-address "0x0000000000000000000000000000000000000000")
|
||||
(def default-key
|
||||
"0x0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
(def default-hash "0x0000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
(defn valid-eth-name-prefix?
|
||||
[prefix]
|
||||
(not
|
||||
(or (string/blank? prefix)
|
||||
(string/ends-with? prefix ".")
|
||||
(string/includes? prefix ".."))))
|
||||
|
||||
(defn is-valid-eth-name?
|
||||
[ens-name]
|
||||
(and ens-name
|
||||
(string? ens-name)
|
||||
(string/ends-with? ens-name ".eth")))
|
||||
|
||||
(defn address
|
||||
[chain-id ens-name cb]
|
||||
{:pre [(utils.ens/is-valid-eth-name? ens-name)]}
|
||||
{:pre [(is-valid-eth-name? ens-name)]}
|
||||
(json-rpc/call {:method "ens_addressOf"
|
||||
:params [chain-id ens-name]
|
||||
:on-success cb
|
||||
@@ -14,7 +37,7 @@
|
||||
|
||||
(defn pubkey
|
||||
([chain-id ens-name on-success on-error]
|
||||
{:pre [(utils.ens/is-valid-eth-name? ens-name)]}
|
||||
{:pre [(is-valid-eth-name? ens-name)]}
|
||||
(json-rpc/call {:method "ens_publicKeyOf"
|
||||
:params [chain-id ens-name]
|
||||
:on-success on-success
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
(ns utils.ens.stateofus
|
||||
(ns status-im.ethereum.stateofus
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[utils.ens.core :as utils.ens]))
|
||||
[status-im.ethereum.ens :as ens]
|
||||
[status-im2.config :as config]))
|
||||
|
||||
(def domain "stateofus.eth")
|
||||
|
||||
@@ -28,8 +29,9 @@
|
||||
|
||||
(def old-registrars
|
||||
(merge
|
||||
{:mainnet "0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"
|
||||
:goerli "0xD1f7416F91E7Eb93dD96A61F12FC092aD6B67B11"}))
|
||||
{:mainnet "0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"}
|
||||
(when config/test-stateofus?
|
||||
{:goerli "0xD1f7416F91E7Eb93dD96A61F12FC092aD6B67B11"})))
|
||||
|
||||
(defn get-cached-registrar
|
||||
[chain]
|
||||
@@ -49,6 +51,6 @@
|
||||
[contact-identity]
|
||||
(when (string? contact-identity)
|
||||
(string/lower-case
|
||||
(if (utils.ens/is-valid-eth-name? contact-identity)
|
||||
(if (ens/is-valid-eth-name? contact-identity)
|
||||
contact-identity
|
||||
(subdomain contact-identity)))))
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns utils.ens.stateofus-test
|
||||
(ns status-im.ethereum.stateofus-test
|
||||
(:require
|
||||
[cljs.test :refer-macros [deftest is]]
|
||||
[utils.ens.stateofus :as stateofus]))
|
||||
[status-im.ethereum.stateofus :as stateofus]))
|
||||
|
||||
(deftest valid-username?
|
||||
(is (false? (stateofus/valid-username? nil)))
|
||||
@@ -74,12 +74,9 @@
|
||||
"blockNumber" blockNumber
|
||||
"accounts" accounts)
|
||||
(case type
|
||||
"new-transfers" (new-transfers cofx blockNumber accounts)
|
||||
"recent-history-fetching" (recent-history-fetching-started cofx accounts)
|
||||
"recent-history-ready" (recent-history-fetching-ended cofx event)
|
||||
"fetching-history-error" (fetching-error cofx event)
|
||||
"non-archival-node-detected" (non-archival-node-detected cofx event)
|
||||
"wallet-owned-collectibles-filtering-done" {:fx [[:dispatch
|
||||
[:wallet/owned-collectibles-filtering-done
|
||||
event]]]}
|
||||
"new-transfers" (new-transfers cofx blockNumber accounts)
|
||||
"recent-history-fetching" (recent-history-fetching-started cofx accounts)
|
||||
"recent-history-ready" (recent-history-fetching-ended cofx event)
|
||||
"fetching-history-error" (fetching-error cofx event)
|
||||
"non-archival-node-detected" (non-archival-node-detected cofx event)
|
||||
(log/warn ::unknown-wallet-event :type type :event event)))
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
status-im.ui.screens.privacy-and-security-settings.events
|
||||
[status-im.utils.dimensions :as dimensions]
|
||||
status-im.utils.logging.core
|
||||
[status-im.utils.universal-links.core :as universal-links]
|
||||
[status-im.utils.utils :as utils]
|
||||
status-im.visibility-status-popover.core
|
||||
status-im.visibility-status-updates.core
|
||||
@@ -52,7 +53,6 @@
|
||||
status-im.wallet.custom-tokens.core
|
||||
[status-im2.common.biometric.events :as biometric]
|
||||
[status-im2.common.theme.core :as theme]
|
||||
[status-im2.common.universal-links :as universal-links]
|
||||
[status-im2.constants :as constants]
|
||||
status-im2.contexts.chat.home.events
|
||||
status-im2.contexts.communities.home.events
|
||||
@@ -118,7 +118,7 @@
|
||||
(let [current-theme-type (get-in cofx [:db :profile/profile :appearance])]
|
||||
(when (and (multiaccounts.model/logged-in? db)
|
||||
(= current-theme-type status-im2.constants/theme-type-system))
|
||||
{:profile.settings/switch-theme-fx
|
||||
{:multiaccounts.ui/switch-theme-fx
|
||||
[(get-in db [:profile/profile :appearance])
|
||||
(:view-id db) true]})))
|
||||
|
||||
@@ -158,9 +158,7 @@
|
||||
|
||||
(rf/defn on-going-in-background
|
||||
[{:keys [db now]}]
|
||||
{:db (-> db
|
||||
(assoc :app-in-background-since now)
|
||||
(dissoc :universal-links/handling))})
|
||||
{:db (assoc db :app-in-background-since now)})
|
||||
;; event not implemented
|
||||
;; :dispatch-n [[:audio-recorder/on-background] [:audio-message/on-background]]
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
:on-success #(re-frame/dispatch [:chat-updated %])}]}))
|
||||
|
||||
(rf/defn create-from-link
|
||||
{:events [:group-chats/create-from-link]}
|
||||
[cofx {:keys [chat-id invitation-admin chat-name]}]
|
||||
(if (get-in cofx [:db :chats chat-id])
|
||||
{:dispatch [:chat/navigate-to-chat chat-id]}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
(ns status-im.integration-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest is]]
|
||||
[clojure.string :as string]
|
||||
[day8.re-frame.test :as rf-test]
|
||||
[native-module.core :as native-module]
|
||||
[re-frame.core :as rf]
|
||||
status-im.events
|
||||
[status-im.multiaccounts.logout.core :as logout]
|
||||
status-im.subs.root ; so integration tests can run independently
|
||||
[status-im.transport.core :as transport]
|
||||
[status-im.utils.test :as utils.test]
|
||||
[status-im2.constants :as constants]
|
||||
status-im2.events
|
||||
status-im2.navigation.core
|
||||
status-im2.subs.root ; so integration tests can run independently
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(def password "testabc")
|
||||
|
||||
(def community {:membership 1 :name "foo" :description "bar"})
|
||||
|
||||
(def account-name "account-abc")
|
||||
|
||||
(utils.test/init!)
|
||||
|
||||
(defn initialize-app!
|
||||
[]
|
||||
(rf/dispatch [:app-started]))
|
||||
|
||||
(defn create-multiaccount!
|
||||
[]
|
||||
(rf/dispatch [:profile.create/create-and-login
|
||||
{:display-name account-name :password password :color "blue"}]))
|
||||
|
||||
(defn assert-app-initialized
|
||||
[]
|
||||
(let [app-state @(rf/subscribe [:app-state])]
|
||||
(is (= "active" app-state))))
|
||||
|
||||
(defn messenger-started
|
||||
[]
|
||||
@(rf/subscribe [:messenger/started?]))
|
||||
|
||||
(defn assert-messenger-started
|
||||
[]
|
||||
(is (messenger-started)))
|
||||
|
||||
(defn assert-community-created
|
||||
[]
|
||||
(is (= @(rf/subscribe [:communities/create]) community)))
|
||||
|
||||
(defn create-new-account!
|
||||
[]
|
||||
(rf/dispatch-sync [:wallet-legacy.accounts/start-adding-new-account {:type :generate}])
|
||||
(rf/dispatch-sync [:set-in [:add-account :account :name] account-name])
|
||||
(rf/dispatch [:wallet-legacy.accounts/add-new-account (native-module/sha3 password)]))
|
||||
|
||||
(defn assert-new-account-created
|
||||
[]
|
||||
(is (true? (some #(= (:name %) account-name)
|
||||
@(rf/subscribe [:profile/wallet-accounts])))))
|
||||
|
||||
(defn logout!
|
||||
[]
|
||||
(rf/dispatch [:logout]))
|
||||
|
||||
(deftest initialize-app-test
|
||||
(log/info "========= initialize-app-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(rf/dispatch [:app-started])
|
||||
(rf-test/wait-for
|
||||
;; use initialize-view because it has the longest avg. time and
|
||||
;; is dispatched by initialize-multiaccounts (last non-view event)
|
||||
[:profile/get-profiles-overview-success]
|
||||
(assert-app-initialized))))
|
||||
|
||||
(deftest create-account-test
|
||||
(log/info "====== create-account-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!) ; initialize app
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))
|
||||
|
||||
(deftest create-community-test
|
||||
(log/info "====== create-community-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!) ; initialize app
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:legacy-only-for-e2e/open-create-community])
|
||||
(doseq [[k v] (dissoc community :membership)]
|
||||
(rf/dispatch-sync [:status-im.communities.core/create-field k v]))
|
||||
(rf/dispatch [:status-im.communities.core/create-confirmation-pressed])
|
||||
(rf-test/wait-for
|
||||
[:status-im.communities.core/community-created]
|
||||
(assert-community-created)
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest create-wallet-account-test
|
||||
(log/info "====== create-wallet-account-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(create-new-account!) ; create a new account
|
||||
(rf-test/wait-for
|
||||
[:wallet-legacy.accounts/account-stored]
|
||||
(assert-new-account-created) ; assert account was created
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest back-up-seed-phrase-test
|
||||
(log/info "========= back-up-seed-phrase-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :step] :12-words]) ; display seed phrase to user
|
||||
(rf/dispatch-sync [:my-profile/enter-two-random-words]) ; begin prompting user for seed words
|
||||
(let [{:keys [mnemonic]} @(rf/subscribe [:profile/profile])
|
||||
seed @(rf/subscribe [:my-profile/seed])
|
||||
word1 (second (:first-word seed))
|
||||
word2 (second (:second-word seed))]
|
||||
(is (= 12 (count (string/split mnemonic #" ")))) ; assert 12-word seed phrase
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :word] word1])
|
||||
(rf/dispatch-sync [:my-profile/set-step :second-word])
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :word] word2])
|
||||
;; TODO: refactor (defn next-handler) & (defn enter-word) to improve test coverage?
|
||||
(rf/dispatch [:my-profile/finish])
|
||||
(rf-test/wait-for
|
||||
[:my-profile/finish-success]
|
||||
(is (nil? @(rf/subscribe [:mnemonic]))) ; assert seed phrase has been removed
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))
|
||||
|
||||
(def multiaccount-name "Narrow Frail Lemming")
|
||||
(def multiaccount-mnemonic
|
||||
"tattoo ramp health green tongue universe style vapor become tape lava reason")
|
||||
(def multiaccount-key-uid "0x694b8229524820a3a00a6e211141561d61b251ad99d6b65daf82a73c9a57697b")
|
||||
|
||||
(def chat-id
|
||||
"0x0402905bed83f0bbf993cee8239012ccb1a8bc86907ead834c1e38476a0eda71414eed0e25f525f270592a2eebb01c9119a4ed6429ba114e51f5cb0a28dae1adfd")
|
||||
|
||||
(deftest one-to-one-chat-test
|
||||
(log/info "========= one-to-one-chat-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest delete-chat-test
|
||||
(log/info "========= delete-chat-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id])
|
||||
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest mute-chat-test
|
||||
(log/info "========= mute-chat-test ==================")
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id true constants/mute-till-unmuted])
|
||||
(rf-test/wait-for
|
||||
[:chat/mute-successfully]
|
||||
(is @(rf/subscribe [:chats/muted chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id false])
|
||||
(rf-test/wait-for
|
||||
[:chat/mute-successfully]
|
||||
(is (not @(rf/subscribe [:chats/muted chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))))
|
||||
|
||||
(deftest add-contact-test
|
||||
(log/info "========= add-contact-test ==================")
|
||||
(let
|
||||
[compressed-key "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gA"
|
||||
public-key
|
||||
"0x048a6773339d11ccf5fd81677b7e54daeec544a1287bd92b725047ad6faa9a9b9f8ea86ed5a226d2a994f5f46d0b43321fd8de7b7997a166e67905c8c73cd37cea"
|
||||
primary-name "zQ3...9cX9gA"]
|
||||
(rf-test/run-test-async
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
;; search for contact using compressed key
|
||||
(rf/dispatch [:contacts/set-new-identity compressed-key])
|
||||
(rf-test/wait-for
|
||||
[:contacts/set-new-identity-success]
|
||||
(let [new-identity @(rf/subscribe [:contacts/new-identity])]
|
||||
(is (= public-key (:public-key new-identity)))
|
||||
(is (= :valid (:state new-identity))))
|
||||
;; click 'view profile' button
|
||||
(rf/dispatch [:chat.ui/show-profile public-key])
|
||||
(rf-test/wait-for
|
||||
[:contacts/build-contact]
|
||||
(rf-test/wait-for
|
||||
[:contacts/contact-built]
|
||||
(let [contact @(rf/subscribe [:contacts/current-contact])]
|
||||
(is (= primary-name (:primary-name contact))))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))))
|
||||
@@ -2,6 +2,7 @@
|
||||
(:require
|
||||
["@react-native-async-storage/async-storage" :default AsyncStorage]
|
||||
["react-native" :refer (BackHandler)]
|
||||
[native-module.core :as native-module]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.keycard.card :as card]
|
||||
[status-im.utils.deprecated-types :as types]
|
||||
@@ -199,6 +200,15 @@
|
||||
(fn [^js listener]
|
||||
(.remove listener)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:keycard/generate-name
|
||||
(fn [{:keys [public-key on-success]}]
|
||||
(native-module/gfycat-identicon-async
|
||||
public-key
|
||||
(fn [whisper-name photo-path]
|
||||
(re-frame/dispatch
|
||||
[on-success whisper-name photo-path])))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:keycard/save-multiaccount-and-login
|
||||
card/save-multiaccount-and-login)
|
||||
|
||||
@@ -179,30 +179,37 @@
|
||||
;; name might have been generated during recovery via passphrase
|
||||
(get-in db [:intro-wizard :derived constants/path-whisper-keyword])
|
||||
{:name name})]
|
||||
(rf/merge cofx
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :setup-step] nil)
|
||||
(dissoc :intro-wizard))}
|
||||
(multiaccounts.create/on-multiaccount-created
|
||||
{:recovered (or recovered (get-in db [:intro-wizard :recovering?]))
|
||||
:derived {constants/path-wallet-root-keyword
|
||||
{:public-key wallet-root-public-key
|
||||
:address (eip55/address->checksum wallet-root-address)}
|
||||
constants/path-whisper-keyword
|
||||
{:public-key whisper-public-key
|
||||
:address (eip55/address->checksum whisper-address)}
|
||||
constants/path-default-wallet-keyword
|
||||
{:public-key wallet-public-key
|
||||
:address (eip55/address->checksum wallet-address)}}
|
||||
:address address
|
||||
:public-key public-key
|
||||
:keycard-instance-uid instance-uid
|
||||
:key-uid (address/normalized-hex key-uid)
|
||||
:keycard-pairing pairing
|
||||
:keycard-paired-on paired-on
|
||||
:chat-key whisper-private-key}
|
||||
encryption-public-key
|
||||
{}))))
|
||||
;; if a name is still `nil` we have to generate it before multiaccount's
|
||||
;; creation otherwise spec validation will fail
|
||||
(if (nil? name)
|
||||
{:keycard/generate-name
|
||||
{:public-key whisper-public-key
|
||||
:on-success ::on-name-generated}}
|
||||
(rf/merge cofx
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :setup-step] nil)
|
||||
(dissoc :intro-wizard))}
|
||||
(multiaccounts.create/on-multiaccount-created
|
||||
{:recovered (or recovered (get-in db [:intro-wizard :recovering?]))
|
||||
:derived {constants/path-wallet-root-keyword
|
||||
{:public-key wallet-root-public-key
|
||||
:address (eip55/address->checksum wallet-root-address)}
|
||||
constants/path-whisper-keyword
|
||||
{:public-key whisper-public-key
|
||||
:address (eip55/address->checksum whisper-address)
|
||||
:name name}
|
||||
constants/path-default-wallet-keyword
|
||||
{:public-key wallet-public-key
|
||||
:address (eip55/address->checksum wallet-address)}}
|
||||
:address address
|
||||
:public-key public-key
|
||||
:keycard-instance-uid instance-uid
|
||||
:key-uid (address/normalized-hex key-uid)
|
||||
:keycard-pairing pairing
|
||||
:keycard-paired-on paired-on
|
||||
:chat-key whisper-private-key}
|
||||
encryption-public-key
|
||||
{})))))
|
||||
|
||||
(rf/defn return-to-keycard-login
|
||||
[{:keys [db] :as cofx}]
|
||||
@@ -374,3 +381,17 @@
|
||||
{:on-card-connected :keycard/load-recovering-key-screen
|
||||
:handler (common/dispatch-event :keycard/import-multiaccount)}))
|
||||
|
||||
(rf/defn on-name-generated
|
||||
{:events [::on-name-generated]
|
||||
:interceptors [(re-frame/inject-cofx :random-guid-generator)
|
||||
(re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]}
|
||||
[{:keys [db] :as cofx} whisper-name]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (update-in db
|
||||
[:keycard :profile/profile]
|
||||
(fn [multiacc]
|
||||
(assoc multiacc
|
||||
:recovered (get db :recovered-account?)
|
||||
:name whisper-name)))}
|
||||
(create-keycard-multiaccount)))
|
||||
|
||||
@@ -265,10 +265,16 @@
|
||||
constants/path-default-wallet]
|
||||
(fn [result]
|
||||
(let [derived-data (multiaccounts.create/normalize-derived-data-keys
|
||||
(types/json->clj result))]
|
||||
(reset! derived-acc
|
||||
{:root-key root-data
|
||||
:derived derived-data}))))))))
|
||||
(types/json->clj result))
|
||||
public-key (get-in derived-data [constants/path-whisper-keyword :public-key])]
|
||||
(native-module/gfycat-identicon-async
|
||||
public-key
|
||||
(fn [name _]
|
||||
(let [derived-data-extended
|
||||
(update derived-data constants/path-whisper-keyword assoc :name name)]
|
||||
(reset! derived-acc
|
||||
{:root-key root-data
|
||||
:derived derived-data-extended})))))))))))
|
||||
(when (= password (get @state :password))
|
||||
(swap! state assoc-in [:application-info :paired?] true)
|
||||
(later #(on-success (str (rand-int 10000000))))))
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
(ns status-im.multiaccounts.core
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[quo.foundations.colors :as colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[react-native.platform :as platform]
|
||||
[status-im.bottom-sheet.events :as bottom-sheet]
|
||||
[status-im.contact.db :as contact.db]
|
||||
[status-im.multiaccounts.update.core :as multiaccounts.update]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im2.common.theme.core :as theme]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.shell.jump-to.utils :as shell.utils]
|
||||
[status-im2.setup.hot-reload :as hot-reload]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
;; validate that the given mnemonic was generated from Status Dictionary
|
||||
(re-frame/reg-fx
|
||||
::validate-mnemonic
|
||||
(fn [[passphrase callback]]
|
||||
(native-module/validate-mnemonic passphrase callback)))
|
||||
|
||||
(defn displayed-name
|
||||
"Use preferred name, display-name, name or alias in that order"
|
||||
[{:keys [name display-name preferred-name alias public-key ens-verified primary-name]}]
|
||||
(let [display-name (if (string/blank? display-name) nil display-name)
|
||||
preferred-name (if (string/blank? preferred-name) nil preferred-name)
|
||||
ens-name (or preferred-name
|
||||
display-name
|
||||
name)]
|
||||
;; Preferred name is our own otherwise we make sure it's verified
|
||||
(if (or preferred-name (and ens-verified name))
|
||||
ens-name
|
||||
(or display-name primary-name alias (gfycat/generate-gfy public-key)))))
|
||||
|
||||
(defn contact-by-identity
|
||||
[contacts contact-identity]
|
||||
(or (get contacts contact-identity)
|
||||
(contact.db/public-key->new-contact contact-identity)))
|
||||
|
||||
(defn contact-two-names-by-identity
|
||||
[contact profile contact-identity]
|
||||
(let [{:keys [public-key preferred-name display-name]} profile
|
||||
{:keys [primary-name secondary-name]} contact
|
||||
me? (= public-key contact-identity)]
|
||||
(if me?
|
||||
[(cond
|
||||
(not (string/blank? preferred-name)) preferred-name
|
||||
(not (string/blank? display-name)) display-name
|
||||
(not (string/blank? primary-name)) primary-name
|
||||
:else (gfycat/generate-gfy contact-identity))]
|
||||
[primary-name secondary-name])))
|
||||
|
||||
(defn displayed-photo
|
||||
[{:keys [images]}]
|
||||
(or (:thumbnail images)
|
||||
(:large images)
|
||||
(first images)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::webview-debug-changed
|
||||
(fn [value]
|
||||
(when platform/android?
|
||||
(native-module/toggle-webview-debug value))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::blank-preview-flag-changed
|
||||
(fn [flag]
|
||||
(native-module/set-blank-preview-flag flag)))
|
||||
|
||||
(rf/defn confirm-wallet-set-up
|
||||
{:events [:multiaccounts.ui/wallet-set-up-confirmed]}
|
||||
[cofx]
|
||||
(multiaccounts.update/multiaccount-update cofx
|
||||
:wallet-set-up-passed?
|
||||
true
|
||||
{}))
|
||||
|
||||
(rf/defn switch-webview-debug
|
||||
{:events [:multiaccounts.ui/switch-webview-debug]}
|
||||
[{:keys [db] :as cofx} value]
|
||||
(rf/merge cofx
|
||||
{::webview-debug-changed value}
|
||||
(multiaccounts.update/multiaccount-update
|
||||
:webview-debug
|
||||
(boolean value)
|
||||
{})))
|
||||
|
||||
(rf/defn switch-preview-privacy-mode
|
||||
{:events [:multiaccounts.ui/preview-privacy-mode-switched]}
|
||||
[{:keys [db] :as cofx} private?]
|
||||
(rf/merge cofx
|
||||
{::blank-preview-flag-changed private?}
|
||||
(multiaccounts.update/multiaccount-update
|
||||
:preview-privacy?
|
||||
(boolean private?)
|
||||
{})))
|
||||
|
||||
(rf/defn switch-webview-permission-requests?
|
||||
{:events [:multiaccounts.ui/webview-permission-requests-switched]}
|
||||
[cofx enabled?]
|
||||
(multiaccounts.update/multiaccount-update
|
||||
cofx
|
||||
:webview-allow-permission-requests?
|
||||
(boolean enabled?)
|
||||
{}))
|
||||
|
||||
(rf/defn switch-default-sync-period
|
||||
{:events [:multiaccounts.ui/default-sync-period-switched]}
|
||||
[cofx value]
|
||||
(multiaccounts.update/multiaccount-update
|
||||
cofx
|
||||
:default-sync-period
|
||||
value
|
||||
{}))
|
||||
|
||||
(rf/defn switch-preview-privacy-mode-flag
|
||||
[{:keys [db]}]
|
||||
(let [private? (get-in db [:profile/profile :preview-privacy?])]
|
||||
{::blank-preview-flag-changed private?}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:multiaccounts.ui/switch-theme-fx
|
||||
(fn [[theme-type view-id reload-ui?]]
|
||||
(let [[theme status-bar-theme nav-bar-color]
|
||||
;; Status bar theme represents status bar icons colors, so opposite to app theme
|
||||
(if (or (= theme-type constants/theme-type-dark)
|
||||
(and (= theme-type constants/theme-type-system)
|
||||
(theme/device-theme-dark?)))
|
||||
[:dark :light colors/neutral-100]
|
||||
[:light :dark colors/white])]
|
||||
(theme/set-theme theme)
|
||||
(re-frame/dispatch [:change-shell-status-bar-style
|
||||
(if (shell.utils/home-stack-open?) status-bar-theme :light)])
|
||||
(when reload-ui?
|
||||
(rf/dispatch [:dissmiss-all-overlays])
|
||||
(hot-reload/reload)
|
||||
(when-not (= view-id :shell-stack)
|
||||
(re-frame/dispatch [:change-shell-nav-bar-color nav-bar-color]))))))
|
||||
|
||||
(rf/defn switch-appearance
|
||||
{:events [:multiaccounts.ui/appearance-switched]}
|
||||
[cofx theme]
|
||||
(rf/merge cofx
|
||||
{:multiaccounts.ui/switch-theme-fx [theme :appearance true]}
|
||||
(multiaccounts.update/multiaccount-update :appearance theme {})))
|
||||
|
||||
(rf/defn switch-theme
|
||||
{:events [:multiaccounts.ui/switch-theme]}
|
||||
[cofx theme view-id]
|
||||
(let [theme (or theme
|
||||
(get-in cofx [:db :profile/profile :appearance])
|
||||
constants/theme-type-dark)]
|
||||
{:multiaccounts.ui/switch-theme-fx [theme view-id false]}))
|
||||
|
||||
(rf/defn switch-profile-picture-show-to
|
||||
{:events [:multiaccounts.ui/profile-picture-show-to-switched]}
|
||||
[cofx id]
|
||||
(rf/merge cofx
|
||||
{:json-rpc/call [{:method "wakuext_changeIdentityImageShowTo"
|
||||
:params [id]
|
||||
:on-success #(log/debug "picture settings changed successfully")}]}
|
||||
(multiaccounts.update/optimistic :profile-pictures-show-to id)))
|
||||
|
||||
(rf/defn switch-appearance-profile
|
||||
{:events [:multiaccounts.ui/appearance-profile-switched]}
|
||||
[cofx id]
|
||||
(multiaccounts.update/multiaccount-update cofx :profile-pictures-visibility id {}))
|
||||
|
||||
(defn clean-path
|
||||
[path]
|
||||
(if path
|
||||
(string/replace-first path #"file://" "")
|
||||
(log/warn "[native-module] Empty path was provided")))
|
||||
|
||||
(rf/defn save-profile-picture
|
||||
{:events [::save-profile-picture]}
|
||||
[cofx path ax ay bx by]
|
||||
(let [key-uid (get-in cofx [:db :profile/profile :key-uid])]
|
||||
(rf/merge cofx
|
||||
{:json-rpc/call [{:method "multiaccounts_storeIdentityImage"
|
||||
:params [key-uid (clean-path path) ax ay bx by]
|
||||
;; NOTE: In case of an error we can show a toast error
|
||||
:on-success #(re-frame/dispatch [::update-local-picture %])}]}
|
||||
(bottom-sheet/hide-bottom-sheet-old))))
|
||||
|
||||
(rf/defn save-profile-picture-from-url
|
||||
{:events [::save-profile-picture-from-url]}
|
||||
[cofx url]
|
||||
(let [key-uid (get-in cofx [:db :profile/profile :key-uid])]
|
||||
(rf/merge cofx
|
||||
{:json-rpc/call [{:method "multiaccounts_storeIdentityImageFromURL"
|
||||
:params [key-uid url]
|
||||
:on-error #(log/error "::save-profile-picture-from-url error" %)
|
||||
:on-success #(re-frame/dispatch [::update-local-picture %])}]}
|
||||
(bottom-sheet/hide-bottom-sheet-old))))
|
||||
|
||||
(comment
|
||||
(re-frame/dispatch
|
||||
[::save-profile-picture-from-url
|
||||
"https://lh3.googleusercontent.com/XuKjNm3HydsaxbPkbpGs9YyCKhn5QQk5oDC8XF2jzmPyYXeZofxFtfUDZuQ3EVmacS_BlBKzbX2ypm37YNX3n1fDJA3WndeFcPsp7Z0=w600"]))
|
||||
|
||||
(rf/defn delete-profile-picture
|
||||
{:events [::delete-profile-picture]}
|
||||
[cofx name]
|
||||
(let [key-uid (get-in cofx [:db :profile/profile :key-uid])]
|
||||
(rf/merge cofx
|
||||
{:json-rpc/call [{:method "multiaccounts_deleteIdentityImage"
|
||||
:params [key-uid]
|
||||
;; NOTE: In case of an error we could fallback to previous image in
|
||||
;; UI with a toast error
|
||||
:on-success #(log/info "[multiaccount] Delete profile image" %)}]}
|
||||
(multiaccounts.update/optimistic :images nil)
|
||||
(bottom-sheet/hide-bottom-sheet-old))))
|
||||
|
||||
(rf/defn get-profile-picture
|
||||
[cofx]
|
||||
(let [key-uid (get-in cofx [:db :profile/profile :key-uid])]
|
||||
{:json-rpc/call [{:method "multiaccounts_getIdentityImages"
|
||||
:params [key-uid]
|
||||
:on-success #(re-frame/dispatch [::update-local-picture %])}]}))
|
||||
|
||||
(rf/defn store-profile-picture
|
||||
{:events [::update-local-picture]}
|
||||
[cofx pics]
|
||||
(multiaccounts.update/optimistic cofx :images pics))
|
||||
|
||||
(comment
|
||||
;; Test seed for Dim Venerated Yaffle, it's not here by mistake, this is just a test account
|
||||
(native-module/validate-mnemonic
|
||||
"rocket mixed rebel affair umbrella legal resemble scene virus park deposit cargo"
|
||||
prn))
|
||||
@@ -2,6 +2,7 @@
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.wallet.core :as wallet]
|
||||
[status-im2.common.keychain.events :as keychain]
|
||||
[status-im2.db :as db]
|
||||
@@ -29,15 +30,15 @@
|
||||
[{:keys [db] :as cofx} {:keys [auth-method logout?]}]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||
(rf/merge cofx
|
||||
{:set-root :progress
|
||||
:chat.ui/clear-inputs nil
|
||||
:shell/reset-state nil
|
||||
:hide-popover nil
|
||||
::logout nil
|
||||
:profile.settings/webview-debug-changed false
|
||||
:keychain/clear-user-password key-uid
|
||||
:profile/get-profiles-overview #(rf/dispatch
|
||||
[:profile/get-profiles-overview-success %])}
|
||||
{:set-root :progress
|
||||
:chat.ui/clear-inputs nil
|
||||
:shell/reset-state nil
|
||||
:hide-popover nil
|
||||
::logout nil
|
||||
::multiaccounts/webview-debug-changed false
|
||||
:keychain/clear-user-password key-uid
|
||||
:profile/get-profiles-overview #(rf/dispatch
|
||||
[:profile/get-profiles-overview-success %])}
|
||||
(keychain/save-auth-method key-uid auth-method)
|
||||
(wallet/clear-timeouts)
|
||||
(initialize-app-db))))
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
constants/path-default-wallet]
|
||||
(fn [result]
|
||||
(let [derived-data (multiaccounts.create/normalize-derived-data-keys
|
||||
(types/json->clj result))]
|
||||
(re-frame/dispatch [success-event root-data derived-data])))))))))
|
||||
(types/json->clj result))
|
||||
public-key (get-in derived-data [constants/path-whisper-keyword :public-key])]
|
||||
(native-module/gfycat-identicon-async
|
||||
public-key
|
||||
(fn [name _]
|
||||
(let [derived-data-extended
|
||||
(update derived-data constants/path-whisper-keyword assoc :name name)]
|
||||
(re-frame/dispatch [success-event root-data derived-data-extended]))))))))))))
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(ns status-im.multiaccounts.update.core
|
||||
(:require
|
||||
[status-im.ethereum.ens :as ens]
|
||||
[status-im.utils.deprecated-types :as types]
|
||||
[status-im2.constants :as constants]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.ens.core :as utils.ens]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/defn send-contact-update
|
||||
@@ -23,9 +23,7 @@
|
||||
(when-let [new-name (and account (or preferred-name display-name name))]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (assoc-in db
|
||||
[:profile/profile :ens-name?]
|
||||
(utils.ens/is-valid-eth-name? new-name))
|
||||
{:db (assoc-in db [:profile/profile :ens-name?] (ens/is-valid-eth-name? new-name))
|
||||
:json-rpc/call [{:method "multiaccounts_updateAccount"
|
||||
:params [(assoc account :name new-name)]
|
||||
:on-success #(log/debug "sent multiaccount update")}]}))))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user