Compare commits

..
Author SHA1 Message Date
Volodymyr Kozieiev 09e922fd05 fx namespaces removed in favor of utils.re-frame 2022-12-16 15:03:55 +00:00
Icaro Motta 269d13e2f8 Move Activity Center to new app structure (#14554) 2022-12-16 10:45:00 -03:00
Siddarth Kumar fc07fbf787 UI for mobile to mobile local pairing - updated (#14514)
* ui for local pairing

lint-fix

removed un-necessary +

addressing some of the feedback on PR

more feedback + removing feature toggle from ui

getting rid of comments/log messages over here

tidy up logs

fix typos and more i18n stuff

swap % with a named parameter

getting rid of global state + lint-fix

get rid of un-used function

icon guidelines and more kebab case stuff :>

moving stuff to events and utils namespace

:main-icons -> :i :)

address feedback and adhere to guidelines etc

fixed the :t/ qualification

moree feedback :-D

referring status-im.utils.security for now

adding "cs" to constants

make tests pass

re-frame to rf

addressing feedback

moving icons to icons2 & renaming stuff

trying to make this file the way it was before

missed out on updating these references

getting rid of the icons moved to icons2

This reverts commit be8552c0d3daaf7a7333cfeaf304d97c86d50d3e.

fixing mistakes

getting rid of the s

* this rename makes sense to me

* adding an alias to the view

* fixed broken up namespaces
2022-12-16 18:40:56 +05:30
Ajay Sivan 621e41e6ab feat: Added Dividers Date Component to quo2 (#14546)
* feat: Added Dividers Date Component  to quo2
2022-12-15 17:18:54 +01:00
Andrea Maria Piana 3e3a365ffb Enable waku2 by default 2022-12-15 12:39:49 +00:00
Ibrahem Khalil 4e6a37fdd0 Message input issues (#14450)
Message input issues (#14450)
2022-12-15 10:49:54 +01:00
Icaro Motta 08fb0de7b0 Remove old Notification Center (#14533) 2022-12-14 18:00:32 -03:00
pavloburykh f47bc6e40e e2e: new community tests 2022-12-14 10:07:46 +01:00
Jamie Caprani 6a05d43b75 chore: add some component tests (#14511) 2022-12-13 14:19:20 -08:00
Volodymyr Kozieiev 68ea7cdfc5 Communities now correctly listed in pending/joined sections. (#14515) 2022-12-13 20:55:32 +00:00
Icaro Motta 5693df5a74 Lint namespace aliases (#14526) 2022-12-13 17:04:26 -03:00
flexsurfer c7a371c104 move messages screen to status-im2 (#14530)
* move messages screen to status-im2
2022-12-13 13:27:13 +01:00
Parvesh Monu 11d02c1057 fix unread badge and add back button accessibility id (#14525) 2022-12-13 14:56:46 +05:30
Churikova Tetiana c13be92505 e2e: new activity center flow 2022-12-09 13:58:54 +01:00
Icaro Motta ae10908984 Set Activity Center as the new default (#14492) 2022-12-09 09:52:18 -03:00
Jamie Caprani 53ef1ef4b5 chore: refactor scroll page component to be more generic (#14453)
chore: refactor scroll page component to be more generic
2022-12-09 04:07:21 -08:00
yqrashawn bd84a36582 feat: new delete message (#14232) 2022-12-09 15:35:41 +08:00
Roman Volosovskyi 9be8a5b961 Add peers stats screen 2022-12-08 13:28:09 +01:00
flexsurfer 5ca46fe805 Bug/composer background fix (#14502)
* [#14385] Chat view freezes if open/close chat multiple times (iOS)
2022-12-07 17:36:26 +01:00
jakub 2f1c236333 ci: update Apple dev team ID to update the org
We are trasnferring the Apple app to the new Switzerland based Apple
development organization/team from the old Singapore one, and to make
this work we also need to update the development team ID.

This should fix the following failure:
```
Could not find App ID with bundle identifier 'im.status.ethereum'
You can easily generate a new App ID on the Developer Portal using 'produce':
```
https://ci.infra.status.im/job/status-mobile/job/platforms/job/ios/854/console

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-12-07 10:45:14 +01:00
Yevheniia Berdnyk f2fa3a1886 Fixed test_group_chat_pin_messages and moved to PR tests 2022-12-06 23:16:55 +02:00
Parvesh Monu e6628bd346 fix app crashing on opening community channel (#14499) 2022-12-06 23:16:28 +05:30
Icaro Motta 6e272a96c8 Introduce subscription tests (#14472) 2022-12-06 13:36:05 -03:00
flexsurfer da0f0d3a81 move messages main screen to status-im2 (#14491)
* move messages main screen to status-im2
2022-12-05 14:22:06 +01:00
frank f7af7ca25d Allow owner/admin to delete messages of a community (#14366) 2022-12-02 20:13:02 +08:00
Christoph Pader f3ad8beb6d fix: channel list-item not clickable (#14469) 2022-12-02 11:45:26 +01:00
yqrashawn 2e68fb22f5 feat: add display-name in author component (#14473) 2022-12-02 14:42:16 +08:00
Christoph Pader f18044c9dc Implement Token Gating (#13899)
* feat: implement token gating component

* fix: add .calva to gitignore
2022-12-01 13:30:07 +00:00
Jamie Caprani 5947769881 chore: update action drawer with designer feedback (#14465) 2022-12-01 03:58:50 -08:00
Parvesh Monu d4897de205 partially implement shell jump-to navigation (#14410) 2022-11-30 21:46:01 +05:30
Jamie Caprani d948939ce3 fix: adjust community cards on discover page (#14444)
fix: adjust community cards on discover page
2022-11-30 01:58:44 -08:00
Yevheniia Berdnyk 1000cb79a2 Creating users and getting names in parallel 2022-11-29 18:09:39 +02:00
Mohamed Javid f55b646c20 [Chore] Use common for repeated code (#14454)
* [Chore] Use common for repeated code

* [Chore] Removed empty file
2022-11-29 22:47:35 +08:00
Mohamed Javid bc7578ae85 [Feature] Added Mentions in Activity Center (#14451)
* [Feature][#14352] Added mentions in Activity Center

* [Update][#14352] Update in namespace

* [Update][#14352] Code Style

* [Update][#14352] Created commons for AC and fix warning on text
2022-11-29 21:55:33 +08:00
Omar Basem 49e9738ff8 Group Details Screen (2) (#14427)
* feat: group details screen (2)
2022-11-29 10:41:19 +04:00
Brian Sztamfater 9e1412007f fix: edit message
Signed-off-by: Brian Sztamfater <brian@status.im>
2022-11-28 10:21:50 -03:00
Icaro Motta c198133769 Update guidelines (#14445) 2022-11-28 09:58:36 -03:00
Jamie Caprani 6d5ff4aa92 chore: fix issues with e2e tests after refactors (#14440) 2022-11-28 03:17:12 -08:00
Jamie Caprani 0e614d51ef feat: add scrolling in community (#14281) 2022-11-28 02:24:31 -08:00
Icaro Motta 42de2a6384 Mark notifications as read based on call to action (#14439) 2022-11-25 16:10:29 -03:00
Ibrahem Khalil 3d71fdba98 [14429] Fix mentions (#14442) 2022-11-25 15:04:15 +02:00
Jamie Caprani 9de4417f03 chore: update nix config to reference babel.config.js file (#14436)
* chore: update nix config to reference babel.config.js file

* chore: update component test make command
2022-11-24 13:05:58 -08:00
Parvesh Monu 9e7db45d08 fix everyone is displaying as online in community channel and chat list (#14431)
also fixes issue with visibility status popover not opening
2022-11-24 19:45:27 +05:30
Omar Basem 14c243803f Photo Selector (1) (#14426)
* feat: photo-selector (1)
2022-11-24 15:29:54 +04:00
jakub e61889f16c ci: add missing suffix to android e2e builds
Fix for same naming of e2e and universal builds.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-11-24 10:23:28 +01:00
Parvesh Monu 1230594046 test build fix (#14435) 2022-11-24 14:50:57 +05:30
jakub 3fc1ba357e ci: bump jenkins lib to match desktop
Related to: https://github.com/status-im/status-desktop/pull/8420
Depends on: https://github.com/status-im/status-jenkins-lib/pull/53

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-11-23 19:48:55 +01:00
flexsurfer 3152cf0aa3 adjust navigation colors and small bugfixes (#14403)
* adjust navigation colors and small bugfixes
2022-11-23 17:30:11 +01:00
Jamie Caprani c6e8aad745 feat: add component tests using react-testing-library and jest (#14331) 2022-11-23 05:59:18 -08:00
flexsurfer bab0fc7ac0 move chats home (#14419)
* move chats home
2022-11-23 14:33:40 +01:00
frank 32d85d5059 Allow styling text in composer when selecting it with native actions (#14249) 2022-11-23 10:28:44 +08:00
Churikova Tetiana a9295ac17e e2e: test sections and fix 2022-11-22 17:44:28 +01:00
jakub 7dccbf2395 ci: separate buckets for mobile and desktop builds
Possible fix for slow upload speeds and failures caused by most probably
hitting per-bucket rate limits of DigitalOcean:

>- 500 total operations per second to any individual bucket.
>- 300 combined PUT, POST, COPY, DELETE, and LIST operations per second to any individual Space. We may further limit LIST operations if necessary under periods of high load.y

https://docs.digitalocean.com/products/spaces/details/limits/#rate-limits

Depends on: https://github.com/status-im/status-jenkins-lib/pull/52

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-11-22 16:55:59 +01:00
410 changed files with 13104 additions and 8459 deletions
-8
View File
@@ -1,8 +0,0 @@
{
"presets": [
"module:metro-react-native-babel-preset"
],
"plugins": [
"react-native-reanimated/plugin"
]
}
+1 -1
View File
@@ -200,7 +200,7 @@ status-im.utils.platform/isMacOs?
status-im.utils.platform/isNix?
status-im.utils.platform/isWin?
status-im.utils.platform/android-version>=
status-im.utils.debounce/clear-all
utils.debounce/clear-all
status-im.transport.db/create-chat
status-im.utils.priority-map/priority-map
status-im.utils.priority-map/priority-map-by
+20 -14
View File
@@ -1,14 +1,20 @@
{:lint-as {status-im.utils.views/defview clojure.core/defn
status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
utils.re-frame/defn clj-kondo.lint-as/def-catch-all
quo.react/with-deps-check clojure.core/fn
quo.previews.preview/list-comp clojure.core/for
status-im.utils.styles/def clojure.core/def
status-im.utils.styles/defn clojure.core/defn
taoensso.tufte/defnp clojure.core/defn}
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn utils.re-frame/defn]}
;;TODO remove number when this is fixed
;;https://github.com/borkdude/clj-kondo/issues/867
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY number]}}}
{:lint-as {status-im.utils.views/defview clojure.core/defn
status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
utils.re-frame/defn clj-kondo.lint-as/def-catch-all
quo.react/with-deps-check clojure.core/fn
quo.previews.preview/list-comp clojure.core/for
status-im.utils.styles/def clojure.core/def
status-im.utils.styles/defn clojure.core/defn
status-im.test-helpers/deftest-sub clojure.core/defn
taoensso.tufte/defnp clojure.core/defn}
:linters {:consistent-alias {:level :error
:aliases {clojure.string string
taoensso.timbre log}}
:invalid-arity {:skip-args [status-im.utils.fx/defn utils.re-frame/defn]}
;; TODO remove number when this is fixed
;; https://github.com/borkdude/clj-kondo/issues/867
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY
number
status-im.test-helpers/restore-app-db]}}}
+2 -1
View File
@@ -4,7 +4,7 @@ DEFAULT_NETWORK=mainnet_rpc
DEV_BUILD=1
ETHEREUM_DEV_CLUSTER=1
EXTENSIONS=0
FLEET=eth.prod
FLEET=status.prod
GROUP_CHATS_ENABLED=1
LOG_LEVEL=info
MAILSERVER_CONFIRMATIONS_ENABLED=1
@@ -32,3 +32,4 @@ COMMANDS_ENABLED=1
TWO_MINUTES_SYNCING=1
SWAP_ENABLED=1
STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
+2 -1
View File
@@ -2,7 +2,7 @@ DEBUG_WEBVIEW=1
DEFAULT_NETWORK=goerli_rpc
ETHEREUM_DEV_CLUSTER=1
EXTENSIONS=0
FLEET=eth.staging
FLEET=status.prod
GROUP_CHATS_ENABLED=1
LOG_LEVEL=debug
MAILSERVER_CONFIRMATIONS_ENABLED=0
@@ -32,3 +32,4 @@ COMMUNITIES_MANAGEMENT_ENABLED=1
DELETE_MESSAGE_ENABLED=1
TWO_MINUTES_SYNCING=1
STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
+2 -1
View File
@@ -3,7 +3,7 @@ DEBUG_WEBVIEW=1
DEFAULT_NETWORK=goerli_rpc
ETHEREUM_DEV_CLUSTER=1
EXTENSIONS=0
FLEET=eth.prod
FLEET=status.prod
GROUP_CHATS_ENABLED=1
LOG_LEVEL=debug
MAILSERVER_CONFIRMATIONS_ENABLED=1
@@ -34,3 +34,4 @@ DELETE_MESSAGE_ENABLED=1
TWO_MINUTES_SYNCING=1
ENABLE_QUO_PREVIEW=1
STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
+1 -1
View File
@@ -2,7 +2,7 @@ DEBUG_WEBVIEW=1
DEFAULT_NETWORK=mainnet_rpc
ETHEREUM_DEV_CLUSTER=1
EXTENSIONS=0
FLEET=eth.prod
FLEET=status.prod
GROUP_CHATS_ENABLED=1
LOG_LEVEL=info
MAILSERVER_CONFIRMATIONS_ENABLED=1
+1 -1
View File
@@ -2,7 +2,7 @@ DEBUG_WEBVIEW=0
DEFAULT_NETWORK=mainnet_rpc
ETHEREUM_DEV_CLUSTER=0
EXTENSIONS=0
FLEET=eth.prod
FLEET=status.prod
GROUP_CHATS_ENABLED=1
LOG_LEVEL=
MAILSERVER_CONFIRMATIONS_ENABLED=1
+7 -1
View File
@@ -7,6 +7,7 @@
# Xcode
#
/component-spec
result/
build/
*.pbxuser
@@ -177,4 +178,9 @@ test/appium/tests/users.py
/lib/
## visual tests
/artifacts
/artifacts
/.calva/
## component-tests
*.log
+18 -5
View File
@@ -307,7 +307,7 @@ test-watch: ##@ Watch tests and re-run no changes to cljs files
test: export TARGET := clojure
test: ##@test Run tests once in NodeJS
# Here we creates the gyp bindings for nodejs
# Here we create the gyp bindings for nodejs
yarn install
yarn shadow-cljs compile mocks && \
yarn shadow-cljs compile test && \
@@ -316,14 +316,27 @@ test: ##@test Run tests once in NodeJS
run-visual-test-ios: export TARGET := clojure
run-visual-test-ios: XCODE_DERIVED_DATA := $(HOME)/Library/Developer/Xcode/DerivedData
run-visual-test-ios: APPLICATION_NAME := $(shell ls $(XCODE_DERIVED_DATA) | grep -E '\bStatusIm-')
run-visual-test-ios: APPLICATION_NAME := StatusIm-brfnruzfrkkycpbndmdoeyrigthc
run-visual-test-ios: export TEST_BINARY_PATH := $(XCODE_DERIVED_DATA)/$(APPLICATION_NAME)/Build/Products/Debug-iphonesimulator/StatusIm.app
run-visual-test-ios: ##@test Run tests once in NodeJS
yarn install
detox build --configuration ios.sim.debug && \
detox test --configuration ios.sim.debug
#--------------
component-test-watch: export TARGET := clojure
component-test-watch: export COMPONENT_TEST := true
component-test-watch: export BABEL_ENV := test
component-test-watch: ##@ Watch tests and re-run no changes to cljs files
yarn install
nodemon --exec 'yarn shadow-cljs compile component-test && jest --config=test/jest/jest.config.js' -e cljs
component-test: export TARGET := clojure
component-test: export COMPONENT_TEST := true
component-test: export BABEL_ENV := test
component-test: ##@test Run tests once in NodeJS
# Here we create the gyp bindings for nodejs
yarn install
yarn shadow-cljs compile component-test && \
jest --config=test/jest/jest.config.js
# Other
#--------------
+24
View File
@@ -0,0 +1,24 @@
module.exports = {
"presets": [
"module:metro-react-native-babel-preset"
],
"plugins": [
"react-native-reanimated/plugin"
],
"env": {
"test": {
"presets": [
'@babel/preset-react',
[
'@babel/preset-env',
{
targets: {
node: '14',
},
},
],
],
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
@@ -36,7 +36,7 @@ pipeline {
LANG = "en_US.UTF-8"
LC_ALL = "en_US.UTF-8"
LANGUAGE = "en_US.UTF-8"
TARGET = 'android'
TARGET = "android${utils.isE2EBuild() ? "-e2e" : ""}"
BUILD_ENV = 'prod'
NIX_CONF_DIR = "${env.WORKSPACE}/nix"
FASTLANE_DISABLE_COLORS = 1
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.3'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -93,6 +93,6 @@ To use a physical iPhone your device UDID must be added to provisioning profiles
- Invite your Apple account to be Developer in Status team.
3. Run a build in XCode using the project from `status-mobile/ios` directory.
- You might see error: `Select a development team in the Signing & Capabilities editor`
- Select `STATUS HOLDINGS PTE. LTD.` as the development team and rebuild again.
- Select `Status Research & Development GmbH` as the development team and rebuild again.
Once build finishes Status should start on your iPhone with its logs in terminal running `make run-metro`.
+88
View File
@@ -62,6 +62,31 @@ with the source file using it. For a real example, see
(do-something)]])
```
#### Styles def vs defn
Always use `def` over `defn`, unless the style relies on dynamic values, such as
deref'ed atoms.
```clojure
;; bad
(defn title-column []
{:height 56})
;; good
(def title-column
{:height 56})
```
```clojure
;; bad
(def community-card
{:background-color (colors/theme-colors colors/white colors/neutral-90)})
;; good
(defn community-card []
{:background-color (colors/theme-colors colors/white colors/neutral-90)})
```
### Using TODOs comments
_TODO_ comments are used extensively in the codebase, but prefer to use them
@@ -249,6 +274,22 @@ test macros `deftest` and `is`, which are ubiquitous in the Clojure community.
(:require [cljs-time.coerce :as time.coerce]))
```
### Javascript interop
Use [binaryage/oops](https://github.com/binaryage/cljs-oops) macros instead of
core interop macros.
```clojure
;; bad
(fn [^js event]
(.-width (.-nativeEvent event)))
;; good
(require '[oops.core :as oops])
(fn [event]
(oops/oget event "nativeEvent.width"))
```
### Accessibility labels
Use keywords instead of strings. As a bonus, remember keywords are cached in
@@ -296,6 +337,53 @@ keywords and concatenating them into a single string.
(i18n/label :t/biometric-auth-error {:code error-code})
```
### Tests
#### Subscription tests
Test [layer-3 subscriptions](https://day8.github.io/re-frame/subscriptions/) by
actually subscribing to them, so reframe's signal graph gets validated too.
```clojure
;; bad
(defn user-recipes
[[current-user all-recipes location]]
...)
(re-frame/reg-sub
:user/recipes
:<- [:current-user]
:<- [:all-recipes]
:<- [:location]
user-recipes)
(deftest user-recipes-test
(testing "builds list of recipes"
(let [current-user {...}
all-recipes {...}
location [...]]
(is (= expected (recipes [current-user all-recipes location]))))))
;; good
(require '[status-im.test-helpers :as h])
(re-frame/reg-sub
:user/recipes
:<- [:current-user]
:<- [:all-recipes]
:<- [:location]
(fn [[current-user all-recipes location]]
...))
(h/deftest-sub :user/recipes
[sub-name]
(testing "builds list of recipes"
(swap! rf-db/app-db assoc
:current-user {...}
:all-recipes {...}
:location [...])
(is (= expected (rf/sub [sub-name])))))
```
## Project Structure
First, the bird's-eye view with some example ClojureScript files:
+46 -23
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
@@ -319,7 +319,6 @@
FBF6AB84EBC2085D28C3CDEF /* Pods-Status-StatusImPR.debug.xcconfig */,
BF23313BEFBC84C6BB7E7677 /* Pods-Status-StatusImPR.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
@@ -401,12 +400,12 @@
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
DevelopmentTeam = DTX7Z4U3YA;
DevelopmentTeam = 8B5X2M6H2Y;
ProvisioningStyle = Manual;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = DTX7Z4U3YA;
DevelopmentTeam = 8B5X2M6H2Y;
LastSwiftMigration = 1140;
ProvisioningStyle = Manual;
SystemCapabilities = {
@@ -425,7 +424,7 @@
};
};
3AAD2AB924A3A60E0075D594 = {
DevelopmentTeam = DTX7Z4U3YA;
DevelopmentTeam = 8B5X2M6H2Y;
ProvisioningStyle = Manual;
};
};
@@ -757,7 +756,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
@@ -768,7 +767,11 @@
);
INFOPLIST_FILE = StatusImTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -791,14 +794,18 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
INFOPLIST_FILE = StatusImTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -818,12 +825,12 @@
BUNDLE_ID_SUFFIX = .debug;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StatusIm/StatusIm.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_IDENTITY = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CUSTOM_PRODUCT_NAME = "Status Debug";
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
EXCLUDED_ARCHS = "";
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -857,7 +864,11 @@
);
INFOPLIST_FILE = StatusIm/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
@@ -888,12 +899,12 @@
BUNDLE_ID_SUFFIX = "";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StatusIm/StatusIm.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CUSTOM_PRODUCT_NAME = Status;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
EXCLUDED_ARCHS = "";
FRAMEWORK_SEARCH_PATHS = (
"$(PROJECT_DIR)/../modules/react-native-status/ios/RCTStatus",
@@ -919,7 +930,11 @@
);
INFOPLIST_FILE = StatusIm/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
@@ -953,7 +968,7 @@
CODE_SIGN_STYLE = Manual;
CUSTOM_PRODUCT_NAME = "Status Debug";
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
FRAMEWORK_SEARCH_PATHS = (
"$(PROJECT_DIR)/../modules/react-native-status/ios/RCTStatus",
"$(inherited)",
@@ -984,7 +999,11 @@
);
INFOPLIST_FILE = StatusImPR/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
@@ -996,7 +1015,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum.pr;
PRODUCT_NAME = "Status PR";
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc im.status.ethereum.pr";
PROVISIONING_PROFILE_SPECIFIER = "match Development im.status.ethereum.pr";
SWIFT_OBJC_BRIDGING_HEADER = "StatusIm-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -1013,12 +1032,12 @@
BUNDLE_ID_SUFFIX = "";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StatusImPR/StatusImPR.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CUSTOM_PRODUCT_NAME = Status;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
FRAMEWORK_SEARCH_PATHS = (
"$(PROJECT_DIR)/../modules/react-native-status/ios/RCTStatus",
"$(inherited)",
@@ -1043,7 +1062,11 @@
);
INFOPLIST_FILE = StatusImPR/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
@@ -1096,7 +1119,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -1179,7 +1202,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = YES;
DEVELOPMENT_TEAM = DTX7Z4U3YA;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -0,0 +1,78 @@
package im.status.ethereum.module;
import android.view.ActionMode;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.views.textinput.ReactEditText;
import javax.annotation.Nonnull;
class RNSelectableTextInputModule extends ReactContextBaseJavaModule {
private ActionMode lastActionMode;
public RNSelectableTextInputModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Nonnull
@Override
public String getName() {
return "RNSelectableTextInputManager";
}
@ReactMethod
public void setupMenuItems(final Integer selectableTextViewReactTag, final Integer textInputReactTag) {
ReactApplicationContext reactContext = this.getReactApplicationContext();
UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
public void execute (NativeViewHierarchyManager nvhm) {
RNSelectableTextInputViewManager rnSelectableTextManager = (RNSelectableTextInputViewManager) nvhm.resolveViewManager(selectableTextViewReactTag);
ReactEditText reactTextView = (ReactEditText) nvhm.resolveView(textInputReactTag);
rnSelectableTextManager.registerSelectionListener(reactTextView);
}
});
}
@ReactMethod
public void startActionMode(final Integer textInputReactTag) {
ReactApplicationContext reactContext = this.getReactApplicationContext();
UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
public void execute (NativeViewHierarchyManager nvhm) {
ReactEditText reactTextView = (ReactEditText) nvhm.resolveView(textInputReactTag);
lastActionMode = reactTextView.startActionMode(reactTextView.getCustomSelectionActionModeCallback(), ActionMode.TYPE_FLOATING);
}
});
}
@ReactMethod
public void hideLastActionMode(){
ReactApplicationContext reactContext = this.getReactApplicationContext();
UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
public void execute (NativeViewHierarchyManager nvhm) {
if(lastActionMode!=null){
lastActionMode.finish();
lastActionMode = null;
}
}
});
}
@ReactMethod
public void setSelection(final Integer textInputReactTag, final Integer start, final Integer end){
ReactApplicationContext reactContext = this.getReactApplicationContext();
UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
public void execute (NativeViewHierarchyManager nvhm) {
ReactEditText reactTextView = (ReactEditText) nvhm.resolveView(textInputReactTag);
reactTextView.setSelection(start, end);
}
});
}
}
@@ -0,0 +1,102 @@
package im.status.ethereum.module;
import android.view.ActionMode;
import android.view.ActionMode.Callback;
import android.view.Menu;
import android.view.MenuItem;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.views.textinput.ReactEditText;
import com.facebook.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class RNSelectableTextInputViewManager extends ReactViewManager {
public static final String REACT_CLASS = "RNSelectableTextInput";
private String[] _menuItems = new String[0];
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public ReactViewGroup createViewInstance(ThemedReactContext context) {
return new ReactViewGroup(context);
}
@ReactProp(name = "menuItems")
public void setMenuItems(ReactViewGroup reactViewGroup, ReadableArray items) {
if(items != null) {
List<String> result = new ArrayList<String>(items.size());
for (int i = 0; i < items.size(); i++) {
result.add(items.getString(i));
}
this._menuItems = result.toArray(new String[items.size()]);
}
}
public void registerSelectionListener(final ReactEditText view) {
view.setCustomSelectionActionModeCallback(new Callback() {
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu.clear();
for (int i = 0; i < _menuItems.length; i++) {
menu.add(0, i, 0, _menuItems[i]);
}
return true;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
int selectionStart = view.getSelectionStart();
int selectionEnd = view.getSelectionEnd();
String selectedText = view.getText().toString().substring(selectionStart, selectionEnd);
// Dispatch event
onSelectNativeEvent(view, item.getItemId(), selectedText, selectionStart, selectionEnd);
mode.finish();
return true;
}
});
}
public void onSelectNativeEvent(ReactEditText view, int eventType, String content, int selectionStart, int selectionEnd) {
WritableMap event = Arguments.createMap();
event.putInt("eventType", eventType);
event.putString("content", content);
event.putInt("selectionStart", selectionStart);
event.putInt("selectionEnd", selectionEnd);
// Dispatch
ReactContext reactContext = (ReactContext) view.getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(view.getId(), "topSelection", event);
}
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.builder()
.put("topSelection", MapBuilder.of("registrationName","onSelection"))
.build();
}
}
@@ -940,6 +940,52 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void getConnectionStringForBootstrappingAnotherDevice(final String configJSON, final Callback callback) throws JSONException {
final JSONObject jsonConfig = new JSONObject(configJSON);
final String keyUID = jsonConfig.getString("keyUID");
final String keyStorePath = this.getKeyStorePath(keyUID);
jsonConfig.put("keystorePath", keyStorePath);
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable runnableTask = new Runnable() {
@Override
public void run() {
String res = Statusgo.getConnectionStringForBootstrappingAnotherDevice(jsonConfig.toString());
callback.invoke(res);
}
};
StatusThreadPoolExecutor.getInstance().execute(runnableTask);
}
@ReactMethod
public void inputConnectionStringForBootstrapping(final String connectionString, final String configJSON, final Callback callback) throws JSONException {
final JSONObject jsonConfig = new JSONObject(configJSON);
final String keyStorePath = pathCombine(this.getNoBackupDirectory(), "/keystore");
jsonConfig.put("keystorePath", keyStorePath);
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable runnableTask = new Runnable() {
@Override
public void run() {
String res = Statusgo.inputConnectionStringForBootstrapping(connectionString,jsonConfig.toString());
callback.invoke(res);
}
};
StatusThreadPoolExecutor.getInstance().execute(runnableTask);
}
@ReactMethod
public void hashTypedData(final String data, final Callback callback) {
Log.d(TAG, "hashTypedData");
@@ -7,6 +7,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -29,12 +30,15 @@ public class StatusPackage implements ReactPackage {
List<NativeModule> modules = new ArrayList<>();
modules.add(new StatusModule(reactContext, this.rootedDevice));
modules.add(new RNSelectableTextInputModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
return Arrays.<ViewManager>asList(
new RNSelectableTextInputViewManager()
);
}
}
@@ -318,6 +318,42 @@ RCT_EXPORT_METHOD(hashMessage:(NSString *)message
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// getConnectionStringForBootstrappingAnotherDevice
RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)configJSON
callback:(RCTResponseSenderBlock)callback) {
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:nil];
NSString *keyUID = [configDict objectForKey:@"keyUID"];
NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID];
NSString *keystoreDir = multiaccountKeystoreDir.path;
[configDict setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO];
NSString *result = StatusgoGetConnectionStringForBootstrappingAnotherDevice(modifiedConfigJSON);
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// inputConnectionStringForBootstrapping
RCT_EXPORT_METHOD(inputConnectionStringForBootstrapping:(NSString *)cs
configJSON:(NSString *)configJSON
callback:(RCTResponseSenderBlock)callback) {
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *multiaccountKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSString *keystoreDir = multiaccountKeystoreDir.path;
[configDict setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO];
NSString *result = StatusgoInputConnectionStringForBootstrapping(cs,modifiedConfigJSON);
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// hashTypedData
RCT_EXPORT_METHOD(hashTypedData:(NSString *)data
callback:(RCTResponseSenderBlock)callback) {
@@ -502,11 +538,11 @@ RCT_EXPORT_METHOD(saveAccountAndLoginWithKeycard:(NSString *)multiaccountData
- (NSString *) getExportDbFilePath {
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"export.db"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
[fileManager removeItemAtPath:filePath error:nil];
}
return filePath;
}
@@ -957,10 +993,10 @@ RCT_EXPORT_METHOD(exportUnencryptedDatabase:(NSString *)accountData
#if DEBUG
NSLog(@"exportUnencryptedDatabase() method called");
#endif
NSString *filePath = [self getExportDbFilePath];
StatusgoExportUnencryptedDatabase(accountData, password, filePath);
callback(@[filePath]);
}
+2 -2
View File
@@ -45,8 +45,8 @@ in stdenv.mkDerivation rec {
filter = lib.mkFilter {
root = path;
include = [
"package.json" "yarn.lock" "metro.config.js" ".babelrc"
"resources/.*" "translations/.*" "src/js/worklet_factory.js"
"package.json" "yarn.lock" "metro.config.js" "babel.config.js"
"resources/.*" "translations/.*" "src/js/.*"
"modules/react-native-status/android.*" "android/.*"
envFileName "VERSION" "status-go-version.json" "react-native.config.js"
];
+8 -2
View File
@@ -74,7 +74,7 @@
"react-native-touch-id": "^4.4.1",
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#refs/tags/v11.16.0-status",
"react-syntax-highlighter": "^15.5.0",
"rn-emoji-keyboard": "git+https://github.com/status-im/rn-emoji-keyboard.git#refs/tags/v0.4.3",
"rn-emoji-keyboard": "0.7.0",
"tdigest": "^0.1.1"
},
"devDependencies": {
@@ -82,15 +82,21 @@
"@babel/helper-builder-react-jsx": "7.0.0",
"@babel/plugin-transform-block-scoping": "7.0.0",
"@babel/preset-env": "7.1.0",
"@babel/preset-react": "^7.18.6",
"@babel/register": "7.0.0",
"@jest/globals": "^25.1.0",
"@mapbox/node-pre-gyp": "^1.0.9",
"@testing-library/jest-native": "^5.0.0",
"@testing-library/react-native": "^11.2.0",
"@types/jest": "^28.1.6",
"detox": "^19.9.1",
"jest": "^25.1.0",
"jest-circus": "^26.0.0",
"jest-image-snapshot": "^5.1.0",
"jest": "^28.1.3",
"nodemon": "^2.0.16",
"nyc": "^14.1.1",
"process": "0.11.10",
"react-test-renderer": "16.13.1",
"rn-snoopy": "git+https://github.com/status-im/rn-snoopy.git#refs/tags/v2.0.2-status",
"shadow-cljs": "2.11.16"
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 KiB

+15 -8
View File
@@ -64,15 +64,15 @@
{:closure-defines
{status-im.utils.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
status-im.utils.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"}
:compiler-options {:output-feature-set :es6
:compiler-options {:output-feature-set :es6
;;disable for android build as there
;;is an intermittent warning with deftype
:warnings-as-errors false
:infer-externs :auto
:static-fns true
:fn-invoke-direct true
:optimizations :advanced
:js-options {:js-provider :closure}}}}
:warnings-as-errors false
:infer-externs :auto
:static-fns true
:fn-invoke-direct true
:optimizations :advanced
:js-options {:js-provider :closure}}}}
;; the tests are ran with node, react-native dependencies are mocked
;; by using node --require override.js, which uses the node-library
;; produced by the target :mocks below and redefines node require
@@ -106,4 +106,11 @@
:output-to "target/mocks/mocks.js"
:output-dir "target/mocks"
:compiler-options {:optimizations :simple
:source-map false}}}}
:source-map false}}
:component-test {:target :npm-module
:entries [quo2.core-spec]
:ns-regexp "-component-spec$"
:output-dir "component-spec"
:compiler-options {:warnings-as-errors false
:static-fns false
:infer-externs true}}}}
+186
View File
@@ -0,0 +1,186 @@
import { useDerivedValue, withTiming, withSequence, withDelay, Easing } from 'react-native-reanimated';
// Shell Worklets
// Home Stack States
const CLOSE_WITH_ANIMATION = 0;
const OPEN_WITH_ANIMATION = 1;
const CLOSE_WITHOUT_ANIMATION = 3;
const OPEN_WITHOUT_ANIMATION = 4;
// Derived Values
export function stackOpacity (stackId, selectedStackId) {
return useDerivedValue(
function () {
'worklet'
return selectedStackId.value == stackId ? 1 : 0;
}
);
}
export function stackPointer (stackId, selectedStackId) {
return useDerivedValue(
function () {
'worklet'
return selectedStackId.value == stackId ? "auto" : "none";
}
);
}
export function bottomTabIconColor (stackId, selectedStackId, homeStackState,
passThrough, selectedTabColor, defaultColor,
passThroughColor) {
return useDerivedValue(
function () {
'worklet'
var homeStackStateValue = homeStackState.value;
if (selectedStackId.value == stackId &&
(homeStackStateValue == OPEN_WITH_ANIMATION ||
homeStackStateValue == OPEN_WITHOUT_ANIMATION)){
return selectedTabColor;
}
else if (passThrough.value){
return passThroughColor;
}
else {
return defaultColor;
}
}
);
}
export function bottomTabsHeight(homeStackState, height, extendedHeight) {
return useDerivedValue(
function () {
'worklet'
switch (homeStackState.value) {
case OPEN_WITH_ANIMATION:
return withTiming(extendedHeight, defaultDurationAndEasing);
break;
case CLOSE_WITH_ANIMATION:
return withTiming(height, defaultDurationAndEasing);
break;
case OPEN_WITHOUT_ANIMATION:
return extendedHeight;
break;
case CLOSE_WITHOUT_ANIMATION:
return height;
break;
}
}
)
}
// Home Stack
const shellAnimationTime = 200;
const defaultDurationAndEasing = {
duration: shellAnimationTime,
easing: Easing.bezier(0, 0, 1, 1),
}
export function homeStackOpacity (homeStackState) {
return useDerivedValue(
function () {
'worklet'
switch (homeStackState.value) {
case OPEN_WITH_ANIMATION:
return withTiming(1, defaultDurationAndEasing);
break;
case CLOSE_WITH_ANIMATION:
return withTiming(0, defaultDurationAndEasing);
break;
case OPEN_WITHOUT_ANIMATION:
return 1;
break;
case CLOSE_WITHOUT_ANIMATION:
return 0;
break;
}
}
);
}
export function homeStackTop (homeStackState, top) {
return useDerivedValue(
function () {
'worklet'
switch (homeStackState.value) {
case OPEN_WITH_ANIMATION:
return withTiming(0, defaultDurationAndEasing);
break;
case CLOSE_WITH_ANIMATION:
return withTiming(top, defaultDurationAndEasing);
break;
case OPEN_WITHOUT_ANIMATION:
return 0;
break;
case CLOSE_WITHOUT_ANIMATION:
return top;
break;
}
}
);
}
export function homeStackLeft (selectedStackId, animateHomeStackLeft, homeStackState, left) {
return useDerivedValue(
function () {
'worklet'
if (animateHomeStackLeft.value) {
var leftValue = left[selectedStackId.value];
switch (homeStackState.value) {
case OPEN_WITH_ANIMATION:
return withSequence(withTiming(leftValue, {duration: 0}), withTiming(0, defaultDurationAndEasing))
break;
case CLOSE_WITH_ANIMATION:
return withTiming(leftValue, defaultDurationAndEasing);
break;
case OPEN_WITHOUT_ANIMATION:
return 0;
break;
case CLOSE_WITHOUT_ANIMATION:
return leftValue;
break;
}
} else {
return 0;
}
}
);
}
export function homeStackPointer (homeStackState) {
return useDerivedValue(
function () {
'worklet'
var homeStackStateValue = homeStackState.value;
return (homeStackStateValue == OPEN_WITH_ANIMATION ||
homeStackStateValue == OPEN_WITHOUT_ANIMATION) ? "auto" : "none";
}
);
}
export function homeStackScale (homeStackState, minimizeScale) {
return useDerivedValue(
function () {
'worklet'
switch (homeStackState.value) {
case OPEN_WITH_ANIMATION:
return withTiming(1, defaultDurationAndEasing);
break;
case CLOSE_WITH_ANIMATION:
return withTiming(minimizeScale, defaultDurationAndEasing);
break;
case OPEN_WITHOUT_ANIMATION:
return 1;
break;
case CLOSE_WITHOUT_ANIMATION:
return minimizeScale;
break;
}
}
);
}
-105
View File
@@ -1,5 +1,3 @@
import { useDerivedValue, withTiming, withSequence, withDelay, Easing } from 'react-native-reanimated';
// Generic Worklets
export function applyAnimationsToStyle(animations, style) {
@@ -29,106 +27,3 @@ export function applyAnimationsToStyle(animations, style) {
return Object.assign(animatedStyle, style);
};
};
// Switcher Worklets
export function stackOpacity (stackId, selectedStackId) {
return useDerivedValue(
function () {
'worklet'
return selectedStackId.value == stackId ? 1 : 0;
}
);
}
export function stackPointer (stackId, selectedStackId) {
return useDerivedValue(
function () {
'worklet'
return selectedStackId.value == stackId ? "auto" : "none";
}
);
}
export function bottomTabIconColor (stackId, selectedStackId, homeStackOpen,
passThrough, selectedTabColor, defaultColor,
passThroughColor) {
return useDerivedValue(
function () {
'worklet'
if (selectedStackId.value == stackId && homeStackOpen.value){
return selectedTabColor;
}
else if (passThrough.value){
return passThroughColor;
}
else {
return defaultColor;
}
}
);
}
// Home Stack
const shellAnimationTime = 200;
const defaultDurationAndEasing = {
duration: shellAnimationTime,
easing: Easing.bezier(0, 0, 1, 1),
}
export function homeStackOpacity (homeStackOpen) {
return useDerivedValue(
function () {
'worklet'
return withTiming(homeStackOpen.value ? 1 : 0, defaultDurationAndEasing);
}
);
}
export function homeStackTop (homeStackOpen, top) {
return useDerivedValue(
function () {
'worklet'
return withTiming(homeStackOpen.value ? 0 : top, defaultDurationAndEasing);
}
);
}
export function homeStackLeft (selectedStackId, animateHomeStackLeft, homeStackOpen, left) {
return useDerivedValue(
function () {
'worklet'
if (animateHomeStackLeft.value) {
var leftValue = left[selectedStackId.value];
if (homeStackOpen.value) {
return withSequence(withTiming(leftValue, {duration: 0}), withTiming(0, defaultDurationAndEasing))
} else {
return withTiming(leftValue, defaultDurationAndEasing);
}
} else {
return 0;
}
}
);
}
export function homeStackPointer (homeStackOpen) {
return useDerivedValue(
function () {
'worklet'
return homeStackOpen.value ? "auto" : "none";
}
);
}
export function homeStackScale (homeStackOpen, minimizeScale) {
return useDerivedValue(
function () {
'worklet'
return withTiming(homeStackOpen.value ? 1 : minimizeScale, defaultDurationAndEasing);
}
);
}
+3
View File
@@ -282,6 +282,8 @@
(def worklet-factory
#js {:applyAnimationsToStyle (fn [])})
(def shell-worklets #js {})
;; Update i18n_resources.cljs
(defn mock [module]
(case module
@@ -328,6 +330,7 @@
"@react-native-async-storage/async-storage" async-storage
"react-native-svg" react-native-svg
"../src/js/worklet_factory.js" worklet-factory
"../src/js/shell_worklets.js" shell-worklets
"./fleets.js" default-fleets
"./chats.js" default-chats
"@walletconnect/client" wallet-connect-client
+1 -1
View File
@@ -34,7 +34,7 @@
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js rn)))
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js rn)))
(def touchable-without-feedback (reagent/adapt-react-class (.-TouchableWithoutFeedback ^js rn)))
(def text-input (reagent/adapt-react-class (.-TextInput ^js rn)))
(def text-input (reagent/adapt-react-class (.-TextInput ^js rn)))
(def keyboard-avoiding-view-class (reagent/adapt-react-class (.-KeyboardAvoidingView ^js rn)))
@@ -21,6 +21,6 @@
:align-items :center
:justify-content :center
:border-radius (/ container-size 2)
:background-color (colors/theme-alpha color 0.5 0.6)}
[icon/icon :i/group {:size icon-size ; TODO: group icon sizes 12 and 20 (small and large) are missing
:background-color (colors/custom-color-by-theme color 50 60)}
[icon/icon :i/group {:size icon-size
:color colors/white-opa-70}]])))
@@ -2,7 +2,7 @@
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
[quo2.components.markdown.text :as text]
[clojure.string :as clojure-string]))
[clojure.string :as string]))
(def circle-sizes {:small 20
:medium 32
@@ -29,10 +29,10 @@
(let [circle-size (size circle-sizes)
small? (= size :small)
f-name-initial (-> f-name
clojure-string/upper-case
string/upper-case
(subs 0 1))
l-name-initial (-> l-name
clojure-string/upper-case
string/upper-case
(subs 0 1))
circle-color (colors/custom-color color 50 20)
text-color (colors/custom-color-by-theme color 50 60)]
+199 -174
View File
@@ -7,147 +7,149 @@
[quo2.components.icon :as quo2.icons]))
(def themes
{:light {:primary {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/primary-50
:pressed colors/primary-60
:disabled colors/primary-50}}
:secondary {:icon-color colors/primary-50
:label {:style {:color colors/primary-50}}
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-40
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/neutral-10
:pressed colors/neutral-20
:disabled colors/neutral-10}}
:dark-grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/neutral-20
:pressed colors/neutral-30
:disabled colors/neutral-20}}
:outline {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:border-color {:default colors/neutral-20
:pressed colors/neutral-40
:disabled colors/neutral-20}}
:ghost {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:background-color {:pressed colors/neutral-10}}
:danger {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/danger-50
:pressed colors/danger-60
:disabled colors/danger-50}}
:positive {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/success-50
:pressed colors/success-60
:disabled colors/success-50-opa-30}}
:photo-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/white-opa-40
:pressed colors/white-opa-50
:disabled colors/white-opa-40}}
:blur-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/neutral-80-opa-5
:pressed colors/neutral-80-opa-10
:disabled colors/neutral-80-opa-5}}
:blur-bg-outline {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}}
:border-color {:default colors/neutral-80-opa-10
:pressed colors/neutral-80-opa-20
:disabled colors/neutral-80-opa-10}}
{:light {:primary {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/primary-50
:pressed colors/primary-60
:disabled colors/primary-50}}
:secondary {:icon-color colors/primary-50
:label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-40
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:background-color {:default colors/neutral-10
:pressed colors/neutral-20
:disabled colors/neutral-10}}
:dark-grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:background-color {:default colors/neutral-20
:pressed colors/neutral-30
:disabled colors/neutral-20}}
:outline {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:border-color {:default colors/neutral-20
:pressed colors/neutral-40
:disabled colors/neutral-20}}
:ghost {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:background-color {:pressed colors/neutral-10}}
:danger {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/danger-50
:pressed colors/danger-60
:disabled colors/danger-50}}
:positive {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/success-50
:pressed colors/success-60
:disabled colors/success-50-opa-30}}
:photo-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label-color colors/neutral-100
:background-color {:default colors/white-opa-40
:pressed colors/white-opa-50
:disabled colors/white-opa-40}}
:blur-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label-color colors/neutral-100
:background-color {:default colors/neutral-80-opa-5
:pressed colors/neutral-80-opa-10
:disabled colors/neutral-80-opa-5}}
:blur-bg-outline {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label-color colors/neutral-100
:border-color {:default colors/neutral-80-opa-10
:pressed colors/neutral-80-opa-20
:disabled colors/neutral-80-opa-10}}
:shell {:icon-color colors/white
:label {:style {:color colors/white}}
:label-color colors/white
:background-color {:default colors/neutral-95
:pressed colors/neutral-95
:disabled colors/neutral-95}}}
:dark {:primary {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/primary-60
:pressed colors/primary-50
:disabled colors/primary-60}}
:secondary {:icon-color colors/primary-50
:label {:style {:color colors/primary-50}}
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-30
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-80
:pressed colors/neutral-60
:disabled colors/neutral-80}}
:dark-grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:outline {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:border-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:ghost {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:background-color {:pressed colors/neutral-80}}
:danger {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/danger-60
:pressed colors/danger-50
:disabled colors/danger-60}}
:positive {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/success-60
:pressed colors/success-50
:disabled colors/success-60-opa-30}}
:photo-bg {:icon-color colors/white
:icon-secondary-color colors/neutral-30
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-80-opa-40
:pressed colors/neutral-80-opa-50
:disabled colors/neutral-80-opa-40}}
:blur-bg {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label {:style {:color colors/white}}
:background-color {:default colors/white-opa-5
:pressed colors/white-opa-10
:disabled colors/white-opa-5}}
:blur-bg-outline {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label {:style {:color colors/white}}
:border-color {:default colors/white-opa-10
:pressed colors/white-opa-20
:disabled colors/white-opa-5}}
:shell {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-95}}}})
:dark {:primary {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/primary-60
:pressed colors/primary-50
:disabled colors/primary-60}}
:secondary {:icon-color colors/primary-50
:label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-30
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label-color colors/white
:background-color {:default colors/neutral-80
:pressed colors/neutral-60
:disabled colors/neutral-80}}
:dark-grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label-color colors/white
:background-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:outline {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label-color colors/white
:border-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:ghost {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label-color colors/white
:background-color {:pressed colors/neutral-80}}
:danger {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/danger-60
:pressed colors/danger-50
:disabled colors/danger-60}}
:positive {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/success-60
:pressed colors/success-50
:disabled colors/success-60-opa-30}}
:photo-bg {:icon-color colors/white
:icon-secondary-color colors/neutral-30
:label-color colors/white
:background-color {:default colors/neutral-80-opa-40
:pressed colors/neutral-80-opa-50
:disabled colors/neutral-80-opa-40}}
:blur-bg {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label-color colors/white
:background-color {:default colors/white-opa-5
:pressed colors/white-opa-10
:disabled colors/white-opa-5}}
:blur-bg-outline {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label-color colors/white
:border-color {:default colors/white-opa-10
:pressed colors/white-opa-20
:disabled colors/white-opa-5}}
:shell {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/neutral-95}}}})
(defn shape-style-container [type icon size]
{:border-radius (if (and icon (#{:primary :secondary :danger} type))
24
(case size
56 12
40 12
32 10
24 8))})
(defn style-container [type size disabled background-color border-color icon above width before after]
(merge {:height size
:align-items :center
:justify-content :center
:flex-direction (if above :column :row)
:border-radius (if (and icon (#{:primary :secondary :danger} type))
24
(case size
56 12
40 12
32 10
24 8))
:background-color background-color
:padding-horizontal (when-not (or icon before after)
(case size 56 16 40 16 32 12 24 8))
@@ -159,6 +161,7 @@
(case size 56 0 40 9 32 5 24 3))
:padding-bottom (when-not (or icon before after)
(case size 56 0 40 9 32 5 24 4))}
(shape-style-container type icon size)
(when width
{:width width})
(when icon
@@ -169,14 +172,19 @@
(when disabled
{:opacity 0.3})))
(defn community-themed? [type community-color]
(and (= type :community) (string? community-color)))
(defn button
"with label
[button opts \"label\"]
opts
{:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/
:danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell
:danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell/:community
:size 40/32/24
:icon true/false
:community-color '#FFFFFF'
:community-text-color '#000000'
:before :icon-keyword
:after :icon-keyword}
@@ -184,13 +192,13 @@
[button {:icon true} :main-icons/close-circle]"
[_ _]
(let [pressed (reagent/atom false)]
(fn [{:keys [on-press disabled type size before after above width
(fn [{:keys [on-press disabled type size community-color community-text-color before after above width
override-theme override-background-color
on-long-press accessibility-label icon icon-no-color style test-ID]
:or {type :primary
size 40}}
children]
(let [{:keys [icon-color icon-secondary-color background-color label border-color]}
(let [{:keys [icon-color icon-secondary-color background-color label-color border-color]}
(get-in themes [(or
override-theme
(theme/get-theme)) type])
@@ -212,48 +220,65 @@
(reset! pressed nil))})
[rn/view {:style (merge
(style-container
type
size
disabled
(or override-background-color (get background-color state))
(get border-color state)
icon
above
width
before
after)
(shape-style-container type icon size)
{:background-color
(if (= state :pressed)
(colors/theme-colors colors/neutral-100 colors/white)
:transparent)}
style)}
(when above
[rn/view
[quo2.icons/icon above {:container-style {:margin-bottom 2}
:color icon-secondary-color
:size icon-size}]])
(when before
[rn/view
[quo2.icons/icon before {:container-style {:margin-left (if (= size 40) 12 8)
:margin-right 4}
[rn/view {:style (merge
(style-container
type
size
disabled
(or override-background-color (get background-color state))
(get border-color state)
icon
above
width
before
after)
(when
(community-themed? type community-color)
(merge
{:background-color community-color}
(when (= state :pressed) {:opacity 0.9}))))}
(when above
[rn/view
[quo2.icons/icon above {:container-style {:margin-bottom 2}
:color icon-secondary-color
:size icon-size}]])
[rn/view
(cond
(or icon icon-no-color)
[quo2.icons/icon children {:color icon-color
:no-color icon-no-color
:size icon-size}]
(when before
[rn/view
[quo2.icons/icon before {:container-style {:margin-left (if (= size 40) 12 8)
:margin-right 4}
:color icon-secondary-color
:size icon-size}]])
[rn/view
(cond
(or icon icon-no-color)
[quo2.icons/icon children {:color icon-color
:no-color icon-no-color
:size icon-size}]
(string? children)
[text/text (merge {:size (when (#{56 24} size) :paragraph-2)
:weight :medium
:number-of-lines 1}
label)
children]
(string? children)
[text/text {:size (when (#{56 24} size) :paragraph-2)
:weight :medium
:number-of-lines 1
:style {:color (if
(and
(community-themed? type community-color)
(string? community-text-color))
community-text-color
label-color)}}
(vector? children)
children)]
(when after
[rn/view
[quo2.icons/icon after {:container-style {:margin-left 4
:margin-right (if (= size 40) 12 8)}
:color icon-secondary-color
:size icon-size}]])]]))))
children]
(vector? children)
children)]
(when after
[rn/view
[quo2.icons/icon after {:container-style {:margin-left 4
:margin-right (if (= size 40) 12 8)}
:color icon-secondary-color
:size icon-size}]])]]]))))
+2 -2
View File
@@ -1,7 +1,7 @@
(ns quo2.components.code.snippet
(:require ["react-native" :as react-native]
[cljs-bean.core :as bean]
[clojure.string :as str]
[clojure.string :as string]
[oops.core :as oops]
[react-native.core :as rn]
[quo2.theme :as theme]
@@ -52,7 +52,7 @@
:style (text-style (get-in node [:properties :className]))}]
(render-nodes children))
;; Remove newlines as we already render each line separately.
(-> value str/trim-newline)))
(-> value string/trim-newline)))
nodes))
(defn- code-block [{:keys [rows line-number-width]}]
@@ -6,15 +6,11 @@
(defn community-card-view-item
[{:keys [name description locked
status tokens cover tags featured]} on-press]
status tokens cover tags width]} on-press]
[rn/touchable-opacity {:on-press on-press}
[rn/view {:style (merge (style/community-card 20)
{:margin-bottom 16}
(if featured
{:margin-right 12}
{:flex 1
:margin-horizontal 20}))}
[rn/view {:style {:height 230
[rn/view {:style (style/community-card 20)}
[rn/view {:style {:width width
:height 230
:border-radius 20}
:on-press on-press}
[rn/view
@@ -28,13 +28,9 @@
[community-stats {:icon :i/group
:members-count "629.2K" ;;TODO here should be formatted value, use money/format-members from outside this component
:icon-color icon-color}]
[community-stats {:icon :i/lightning
[community-stats {:icon :i/lightning
:members-count "112.1K"
:icon-color icon-color}]
(when (= type :card-view)
[community-stats {:icon :i/placeholder
:count 4
:icon-color icon-color}])]))
:icon-color icon-color}]]))
(defn community-tags [tags]
[rn/view (style/community-tags-container)
@@ -69,10 +65,12 @@
:style {:margin-top (if (= size :large) 8 2)}}
description])])
(defn permission-tag-container [{:keys [locked? tokens]}]
(defn permission-tag-container [{:keys [locked? tokens on-press]}]
[permission/tag {:background-color (colors/theme-colors
colors/neutral-10
colors/neutral-80)
:locked? locked?
:tokens tokens
:size 24}])
:size 24
:on-press on-press}])
@@ -0,0 +1,259 @@
(ns quo2.components.community.token-gating
(:require [quo.react-native :as rn]
[quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.buttons.button :as button]
[quo2.components.icon :as icon]
[quo2.components.info.information-box :as information-box]
[quo2.components.markdown.text :as text]
[quo2.components.tags.token-tag :as token-tag]
[quo2.foundations.colors :as colors]
[status-im.i18n.i18n :as i18n]
[status-im.ui.components.fast-image :as fast-image]))
(def ^:private token-tag-horizontal-spacing 7)
(def token-tag-vertical-spacing 5)
(def styles {:container {:flex 1
:padding-horizontal 20}
:header-container {:flex-direction :row
:align-items :center}
:header-spacing-community {:margin-bottom 16}
:header-spacing-channel {:margin-bottom 24}
:header-avatar {:margin-right 8}
:header-community-avatar-image {:width 32 :height 32}
:header-title-container {:flex 1
:flex-direction :row
:align-items :center}
:header-title-lock {:margin-left 6}
:header-info-button {:margin-left 8}
:token-requirement-text-spacing {:margin-vertical 8}
:token-requirement-list-spacing {:padding-vertical (- 8 token-tag-vertical-spacing)}
:token-row {:flex-direction :row
:flex-wrap :wrap
:align-items :center}
:token-row-container {:flex-direction :row
:align-items :center
:margin-horizontal (- token-tag-horizontal-spacing)}
:token-row-container-no-shift {:margin-left 0}
:token-row-or-text {:margin-right 3}
:token-tag-spacing {:margin-horizontal token-tag-horizontal-spacing
:margin-vertical token-tag-vertical-spacing}
:divider {:height 1
:margin-horizontal -20
:margin-top (- 12 token-tag-vertical-spacing)
:margin-bottom 8}
:membership-request-denied {:margin-top 13}
:enter-button {:margin-top 16}
:info-text {:margin-top 12
:text-align :center
:padding-horizontal 20}})
(defn multiple-token-requirements? [token-requirement-lists]
(when (vector? (first token-requirement-lists))
(> (count token-requirement-lists) 1)))
(defn is-token-requirement-met? [token-requirement-list]
(and (seq token-requirement-list)
(every?
(fn [token]
(get token :is-sufficient?))
token-requirement-list)))
(defn are-multiple-token-requirements-met? [token-requirement-lists]
(if (multiple-token-requirements? token-requirement-lists)
(some is-token-requirement-met? token-requirement-lists)
(is-token-requirement-met? token-requirement-lists)))
(defn token-requirement-list-row [tokens community-color]
[rn/view {:style (merge
(get styles :token-row)
(get styles :token-requirement-list-spacing))}
(map-indexed (fn [token-index token]
(let [{:keys [token-img-src token amount is-sufficient? is-purchasable?]} token]
^{:key token-index}
[rn/view {:style (get styles :token-tag-spacing)}
[token-tag/token-tag {:token token
:value amount
:size 24
:border-color community-color
:is-required is-sufficient?
:is-purchasable is-purchasable?
:token-img-src token-img-src}]]))
tokens)])
(defn token-requirement-list [props community-color]
(let [{:keys [gate token-requirements-changed? required-tokens-lost?]} props
[gate-type token-requirement-lists] gate
multiple-token-requirements? (multiple-token-requirements? token-requirement-lists)
is-sufficient? (are-multiple-token-requirements-met? token-requirement-lists)
you-must-hold-label (if (= gate-type :join)
(cond
token-requirements-changed? :t/you-must-now-hold
required-tokens-lost? :t/you-must-always-hold
:else :t/you-must-hold)
:t/you-must-hold)
message-label (cond
(= gate-type :join)
(cond
token-requirements-changed? :t/community-join-requirements-changed
required-tokens-lost? :t/community-join-requirements-tokens-lost
:else (if is-sufficient? :t/community-join-requirements-met :t/community-join-requirements-not-met))
(= gate-type :read)
(if is-sufficient?
:t/community-channel-read-requirements-met
:t/community-channel-read-requirements-not-met)
(= gate-type :write)
(if is-sufficient?
:t/community-channel-write-requirements-met
:t/community-channel-write-requirements-not-met))]
[rn/view
[rn/view {:style (get styles :token-requirement-text-spacing)}
[text/text {:weight :medium
:number-of-lines 1}
(i18n/label
message-label)]
[text/text {:size :paragraph-2
:number-of-lines 1}
(i18n/label
you-must-hold-label)]]
(if multiple-token-requirements?
(map-indexed
(fn [token-requirement-index tokens]
^{:key token-requirement-index}
[rn/view {:style (merge
(get styles :token-row-container)
(when-not (= token-requirement-index 0)
(get styles :token-row-container-no-shift)))}
(when-not (= token-requirement-index 0)
[text/text {:style (get styles :token-row-or-text)
:size :paragraph-2} "or"])
[token-requirement-list-row tokens community-color]])
token-requirement-lists)
[rn/view {:style (get styles :token-row-container)}
[token-requirement-list-row token-requirement-lists community-color]])]))
(defn is-community-locked? [community]
(not (are-multiple-token-requirements-met? (get-in community [:gates :join]))))
(defn is-channel-locked? [channel]
(not (are-multiple-token-requirements-met?
(or (get-in channel [:gates :read])
(get-in channel [:gates :write])))))
(defn token-gating
"[token-gating opts]
opts
{
:community {
:name string
:community-avatar-img-src string
:community-color string
:community-text-color string
:token-requirements-changed? boolean
:required-tokens-lost? boolean
}
:channel {
:name string
:emoji string
:emoji-background-color :string
:on-enter-channel callback
:membership-request-denied? boolean
}
}"
[_ _]
(fn [{:keys [community channel]}]
(let [type (if (some? community) :community :channel)
{:keys [name
emoji
emoji-background-color
on-enter-channel
community-avatar-img-src
community-color
community-text-color
membership-request-denied?
token-requirements-changed?
required-tokens-lost?
gates]} (if (= type :community) community channel)
locked? (if
(= type :community)
(is-community-locked? community)
(is-channel-locked? channel))]
[rn/view {:style (merge (get styles :container) {:background-color (colors/theme-colors colors/white colors/neutral-90)})}
[rn/view {:style (merge
(get styles :header-container)
(if
(= type :community)
(get styles :header-spacing-community)
(get styles :header-spacing-channel)))}
[rn/view {:style (get styles :header-avatar)}
(if (= type :community)
[fast-image/fast-image {:source community-avatar-img-src
:style (get styles :header-community-avatar-image)}]
[channel-avatar/channel-avatar {:big? true
:locked? locked?
:emoji emoji
:emoji-background-color emoji-background-color}])]
[rn/view {:style (get styles :header-title-container)}
[text/text {:weight :semi-bold
:number-of-lines 1
:size :heading-1
:style (get styles :header-text)} (if (= type :community) name (str "# " name))]
(when (= type :community)
[icon/icon (if locked?
:main-icons2/locked
:main-icons2/unlocked)
{:container-style (get styles :header-title-lock)
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)}])]
[button/button {:type :outline
:size 32
:icon true
:style (get styles :header-info-button)} :main-icons2/info]]
(map-indexed (fn [gate-index gate]
^{:key gate-index}
[:<>
(when-not (= gate-index 0)
[rn/view {:style (merge
(get styles :divider)
{:background-color (colors/theme-colors
colors/neutral-10
colors/neutral-80)})}])
[token-requirement-list {:gate gate :token-requirements-changed? token-requirements-changed? :required-tokens-lost? required-tokens-lost?} community-color]])
gates)
(when (= type :channel)
(if membership-request-denied?
[information-box/information-box {:type :error
:icon :main-icons2/untrustworthy
:no-icon-color? true
:style (get styles :membership-request-denied)}
(i18n/label
:t/membership-request-denied)]
[:<>
[button/button {:type :community
:community-color community-color
:community-text-color community-text-color
:style (get styles :enter-button)
:disabled locked?
:on-press on-enter-channel}
(str "# " (i18n/label
:t/enter-channel))]
[text/text
{:size :paragraph-2
:style (merge
(get styles :info-text)
{:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)})}
(i18n/label
:t/community-enter-channel-info)]]))])))
@@ -0,0 +1,34 @@
(ns quo2.components.counter.--tests--.counter-component-spec
(:require ["@testing-library/react-native" :as rtl]
[quo2.components.counter.counter :as counter]
[reagent.core :as reagent]))
(defn render-counter
([]
(render-counter {} nil))
([opts value]
(rtl/render (reagent/as-element [counter/counter opts value]))))
(js/global.test "default render of counter component"
(fn []
(render-counter)
(-> (js/expect (rtl/screen.getByTestId "counter-component"))
(.toBeTruthy))))
(js/global.test "renders counter with a string value"
(fn []
(render-counter {} "1")
(-> (js/expect (rtl/screen.getByText "1"))
(.toBeTruthy))))
(js/global.test "renders counter with an integer value"
(fn []
(render-counter {} 1)
(-> (js/expect (rtl/screen.getByText "1"))
(.toBeTruthy))))
(js/global.test "renders counter with value 99+ when the value is greater than 99"
(fn []
(render-counter {} "100")
(-> (js/expect (rtl/screen.getByText "99+"))
(.toBeTruthy))))
+5 -2
View File
@@ -20,7 +20,7 @@
(defn counter
"type: default, secondary, grey, outline
value: integer"
[{:keys [type override-text-color override-bg-color style]} value]
[{:keys [type override-text-color override-bg-color style accessibility-label]} value]
(let [type (or type :default)
text-color (or override-text-color
(if (or
@@ -38,7 +38,10 @@
1 16
2 20
28)]
[rn/view {:style (cond-> (merge
[rn/view {:test-ID :counter-component
:accessible true
:accessibility-label accessibility-label
:style (cond-> (merge
{:align-items :center
:justify-content :center
:border-radius 6
+18
View File
@@ -0,0 +1,18 @@
(ns quo2.components.dividers.date
(:require [react-native.core :as rn]
[quo2.components.separator :as separator]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]))
(defn date [value]
[rn/view {:margin-vertical 16
:padding-right 8
:padding-left 62}
[text/text {:weight :medium
:accessibility-label :divider-date-text
:size :label
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:text-transform :capitalize
:margin-bottom 4}}
value]
[separator/separator]])
@@ -0,0 +1,63 @@
(ns quo2.components.drawers.--tests--.action-drawers-component-spec
(:require ["@testing-library/react-native" :as rtl]
[quo2.components.drawers.action-drawers :as action-drawer]
[reagent.core :as reagent]))
(defn render-action-drawer
([options]
(rtl/render (reagent/as-element [action-drawer/action-drawer options]))))
(js/global.test "action-drawer renders with elements label displaying"
(fn []
(render-action-drawer [[{:icon :i/friend
:label "a sample label"}]])
(-> (js/expect (rtl/screen.getByText "a sample label"))
(.toBeTruthy))))
(js/global.test "action-drawer renders with elements sub-label displaying"
(fn []
(render-action-drawer [[{:icon :i/friend
:label "a sample label"
:sub-label "a sample sub label"}]])
(-> (js/expect (rtl/screen.getByText "a sample sub label"))
(.toBeTruthy))))
(js/global.test "action-drawer on click action works on element"
(let [event (js/jest.fn)]
(fn []
(render-action-drawer [[{:icon :i/friend
:label "a sample label"
:on-press event}]])
(rtl/fireEvent.press (rtl/screen.getByText "a sample label"))
(-> (js/expect event)
(.toHaveBeenCalled)))))
(js/global.test "action-drawer renders two icons when set"
(fn []
(render-action-drawer [[{:icon :i/friend
:label "a sample label"
:right-icon :i/friend
:accessibility-label :first-element}]])
(-> (js/expect (rtl/screen.getByLabelText "right-icon-for-action"))
(.toBeTruthy))
(-> (js/expect (rtl/screen.queryByLabelText "left-icon-for-action"))
(.toBeTruthy))))
(js/global.test "action-drawer does not render a divider when the add-divider? prop is false"
(fn []
(render-action-drawer [[{:icon :i/friend
:label "a sample label"
:add-divider? false
:accessibility-label :first-element}]])
(-> (js/expect (rtl/screen.getAllByLabelText "divider"))
(.not)
(.toBeTruthy))))
(js/global.test "action-drawer renders a divider when the add-divider? prop is true"
(fn []
(render-action-drawer [[{:icon :i/friend
:label "a sample label"
:add-divider? true
:accessibility-label :first-element}]])
(-> (js/expect (rtl/screen.getAllByLabelText "divider"))
(.toBeTruthy))))
+24 -11
View File
@@ -6,16 +6,19 @@
(defn- get-icon-color [danger?]
(if danger?
(colors/theme-colors colors/danger-50 colors/danger-60)
colors/danger-50
(colors/theme-colors colors/neutral-50 colors/neutral-40)))
(defn divider []
(def divider
[rn/view {:style {:border-top-width 1
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:border-top-color (colors/theme-colors
colors/neutral-10 colors/neutral-90)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row}}])
:flex-direction :row}
:accessible true
:accessibility-label :divider}])
(defn action [{:keys [icon
label
@@ -23,16 +26,24 @@
right-icon
danger?
on-press
add-divider?] :as action-props}]
add-divider?
accessibility-label] :as action-props}]
(when action-props
[:<> {:key label}
(when add-divider? [divider])
[rn/touchable-opacity {:on-press on-press}
(when add-divider? divider)
[rn/touchable-highlight {:accessibility-label accessibility-label
:style {:border-radius 12
:height (if sub-label 58 50)
:margin-horizontal 8}
:underlay-color (colors/theme-colors colors/neutral-5 colors/neutral-90)
:on-press on-press}
[rn/view {:style
{:height (if sub-label 56 47)
:margin-horizontal 20
{:height (if sub-label 58 50)
:margin-horizontal 12
:flex-direction :row}}
[rn/view {:style
[rn/view {:accessibility-label :left-icon-for-action
:accessible true
:style
{:height 20
:margin-top :auto
:margin-bottom :auto
@@ -63,7 +74,9 @@
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20}}
:width 20}
:accessible true
:accessibility-label :right-icon-for-action}
[icon/icon right-icon
{:color (get-icon-color danger?)
:size 20}]])]]]))
+12 -7
View File
@@ -1,24 +1,29 @@
(ns quo2.components.icons.icons
(:require [clojure.java.io :as io]
[clojure.string :as cstr]))
[clojure.string :as string]))
(def icon-path "./resources/images/icons2/")
(defn combine-path [path el]
(if (System/getenv "COMPONENT_TEST")
(str "." path el "@2x.png")
(str "." path el ".png")))
(defn require-icon [size path]
(fn [el]
(let [s (str "." path el ".png")
(let [s (combine-path path el)
k (-> el
(cstr/replace "_" "-")
(cstr/replace " " "-")
(cstr/lower-case)
(string/replace "_" "-")
(string/replace " " "-")
(string/lower-case)
(str size))]
[k `(js/require ~s)])))
(defn get-files [path]
(->> (io/file path)
file-seq
(filter #(cstr/ends-with? % "png"))
(map #(first (cstr/split (.getName %) #"@")))
(filter #(string/ends-with? % "png"))
(map #(first (string/split (.getName %) #"@")))
distinct))
(defn get-icons [size]
+9 -7
View File
@@ -19,19 +19,21 @@
(defn info-message
"[info-message opts \"message\"]
opts
{:type :default/:success/:error
:size :default/:tiny
:icon :main-icons/info ;; info message icon
:text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override"
[{:keys [type size icon text-color icon-color]} message]
{:type :default/:success/:error
:size :default/:tiny
:icon :main-icons/info ;; info message icon
:text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override
:no-icon-color? false ;; disable tint color for icon"
[{:keys [type size icon text-color icon-color no-icon-color?]} message]
(let [weight (if (= size :default) :regular :medium)
size (if (= size :default) :paragraph-2 :label)
text-color (or text-color (get-color type))
icon-color (or icon-color text-color)]
[rn/view {:style {:flex-direction :row
:flex 1}}
[quo2.icons/icon icon {:color icon-color
[quo2.icons/icon icon {:color icon-color
:no-color no-icon-color?
:size 12
:container-style {:margin-top 3}}]
[text/text {:size size
@@ -49,11 +49,12 @@
:closed? true/false ;; information box's state
:id :information-box-id ;; unique id (required for closable? information box)
:icon :main-icons/info ;; information box icon
:no-icon-color? false ;; disable tint color for icon
:style style
:button-label \"PressMe\" ;; add action button with label
:on-button-press action ;; (required for information box with button-label)
:on-close on-close ;; (optional on-close call)"
[{:keys [type closable? closed? id icon style button-label on-button-press on-close]} message]
[{:keys [type closable? closed? id icon style button-label on-button-press on-close no-icon-color?]} message]
(let [background-color (get-color-by-type type :bg)
border-color (get-color-by-type type :border)
icon-color (get-color-by-type type :icon)
@@ -73,7 +74,8 @@
[info-message/info-message {:size :default
:icon icon
:text-color text-color
:icon-color icon-color} message]
:icon-color icon-color
:no-icon-color? no-icon-color?} message]
(when closable?
[rn/touchable-opacity
{:on-press on-close
+50 -49
View File
@@ -1,56 +1,57 @@
(ns quo2.components.list-items.channel
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
(:require [quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.counter.counter :as quo2.counter]
[quo2.components.icon :as quo2.icons]
[quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.markdown.text :as quo2.text]
[quo2.theme :as theme]))
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]))
(defn list-item [{:keys [name locked? mentions-count unread-messages?
muted? is-active-channel? emoji channel-color]
muted? is-active-channel? emoji channel-color on-press]
:or {channel-color colors/primary-50}}]
[rn/view {:style (merge {:height 48
:display :flex
:border-radius 12
:flex-direction :row
:justify-content :space-between
:align-items :center
:width "100%"
:padding-left 12
:padding-right 12}
(when is-active-channel?
{:background-color (colors/theme-alpha channel-color 0.05 0.05)}))}
[rn/view {:display :flex
:flex-direction :row
:justify-content :flex-start
:align-items :center}
[channel-avatar/channel-avatar
{:big? true
:locked? locked?
:emoji-background-color (colors/theme-alpha channel-color 0.1 0.1)
:emoji emoji}]
[quo2.text/text
{:style (merge {:margin-left 12}
(when (and (not locked?) muted?)
{:color (if (theme/dark?) colors/neutral-60 colors/neutral-40)}))
:weight :medium :size :paragraph-1} (str "# " name)]]
[rn/view {:style {:height 20}}
(when (and (not locked?)
muted?)
[quo2.icons/icon :i/muted
{:size 20
:no-color true}])
(when (and (not locked?)
(not muted?)
(pos? (int mentions-count)))
[rn/view {:style {:margin-right 2
:margin-top 2}}
[quo2.counter/counter {:override-bg-color channel-color} mentions-count]])
(when (and (not locked?)
(not muted?)
(not (pos? (int mentions-count)))
unread-messages?)
[quo2.icons/icon :i/channel-notification
{:size 20
:no-color true}])]])
[rn/touchable-opacity {:on-press on-press}
[rn/view {:style (merge {:height 48
:display :flex
:border-radius 12
:flex-direction :row
:justify-content :space-between
:align-items :center
:width "100%"
:padding-left 12
:padding-right 12}
(when is-active-channel?
{:background-color (colors/theme-alpha channel-color 0.05 0.05)}))}
[rn/view {:display :flex
:flex-direction :row
:justify-content :flex-start
:align-items :center}
[channel-avatar/channel-avatar
{:big? true
:locked? locked?
:emoji-background-color (colors/theme-alpha channel-color 0.1 0.1)
:emoji emoji}]
[quo2.text/text
{:style (merge {:margin-left 12}
(when (and (not locked?) muted?)
{:color (if (theme/dark?) colors/neutral-60 colors/neutral-40)}))
:weight :medium :size :paragraph-1} (str "# " name)]]
[rn/view {:style {:height 20}}
(when (and (not locked?)
muted?)
[quo2.icons/icon :i/muted
{:size 20
:no-color true}])
(when (and (not locked?)
(not muted?)
(pos? (int mentions-count)))
[rn/view {:style {:margin-right 2
:margin-top 2}}
[quo2.counter/counter {:override-bg-color channel-color} mentions-count]])
(when (and (not locked?)
(not muted?)
(not (pos? (int mentions-count)))
unread-messages?)
[quo2.icons/icon :i/channel-notification
{:size 20
:no-color true}])]]])
@@ -1,40 +0,0 @@
(ns quo2.components.list-items.user-list.style
(:require [quo2.foundations.colors :as colors]))
(defn user-list-wrapper [can-be-invited? background-type]
(let [height (if can-be-invited? 56 80)]
{:width 359
:border-radius 12
:padding-vertical 8
:padding-horizontal 12
:height height
:background-color (case background-type
:5 colors/primary-50-opa-5
:10 colors/primary-50-opa-10
:transparent :transparent)
:flex-direction :column
:justify-content :center}))
(def ml-8 {:margin-left 8})
(def row-centered {:flex-direction :row
:align-items :center})
(def icon-container-styles {:width 32
:height 32
:border-radius 10
:border-width 1
:border-color (colors/theme-colors colors/neutral-80-opa-10 colors/white-opa-10)
:justify-content :center
:align-items :center})
(def container (assoc row-centered :justify-content :space-between))
(def cant-be-invited {:flex-direction :row
:margin-top 8
:margin-left 40})
(def icon-container {:margin-right 2
:margin-top 2})
(def cant-be-invited-text-style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)})
@@ -1,67 +0,0 @@
(ns quo2.components.list-items.user-list.view
(:require [quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.icon :as icons]
[quo2.components.list-items.user-list.style :as style]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[quo2.components.selectors.selectors :as selectors]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.utils.utils :as utils]))
(def ^:private icon-types {:options :i/options
:close :i/close})
(defn- cant-be-invited-msg []
[rn/view {:style style/cant-be-invited
:accessibility-label "user can't be invite"}
[icons/icon :i/info {:size 12
:color colors/neutral-40
:container-style style/icon-container}]
[text/text {:weight :regular
:size :label
:style style/cant-be-invited-text-style}
"Cant be invited due to their privacy settings"]])
(defn- right-section [{:keys [id button-type on-press checked?]}]
(if (= button-type :checkbox)
[selectors/checkbox {:default-checked? checked?}]
[rn/touchable-opacity
{:on-press on-press
:accessibility-label (str (or id "user-list") "-" (if (= button-type :options)
"options"
"close"))}
[rn/view {:style (when (= button-type :close)
style/icon-container-styles)}
[icons/icon (button-type icon-types) {:size 20
:color (colors/theme-colors colors/neutral-100 colors/white)}]]]))
(defn- user-info [{:keys [id contact on-press checked? button-type]}]
[rn/view {:style style/container}
[rn/view style/row-centered
[user-avatar/user-avatar {:size :small
:ring? true
:online? true
:profile-picture (multiaccounts/displayed-photo contact)
:status-indicator? true}]
[rn/view {:style style/ml-8}
[text/text {:weight :semi-bold
:size :paragraph-1
:style {:color (colors/theme-colors colors/neutral-100 colors/white)}} (-> contact
(multiaccounts/contact-two-names false))]
[text/text {:weight :regular
:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}} (utils/get-shortened-address (:public-key contact))]]]
[right-section {:id id
:button-type button-type
:on-press on-press
:checked? checked?}]])
(defn user-list [{:keys [id contact on-press background-type checked? button-type can-be-invited?]}]
[rn/view {:style (style/user-list-wrapper can-be-invited? background-type)}
[user-info {:id id
:on-press on-press
:checked? checked?
:button-type button-type
:contact contact}]
(when-not can-be-invited? [cant-be-invited-msg])])
@@ -1,6 +1,6 @@
(ns status-im.ui.screens.chat.components.messages-skeleton
(:require [status-im.ui.components.react :as react]
[react-native.core :as rn]
(ns quo2.components.loaders.skeleton
(:require [react-native.core :as rn]
[react-native.masked-view :as masked-view]
[reagent.core :as reagent]
[react-native.reanimated :as reanimated]
[quo2.foundations.colors :as colors]))
@@ -34,7 +34,7 @@
{:width window-width
:height "100%"})]
(reanimated/animate-shared-value-with-repeat translate-x window-width 1000 :linear (- 1) false)
[react/masked-view
[masked-view/masked-view
{:style {:height message-skeleton-height}
:maskElement (reagent/as-element
[rn/view {:style {:height message-skeleton-height
@@ -67,11 +67,11 @@
:end {:x 1 :y 0}
:style animated-gradient-style}]]]))])
(defn messages-skeleton [parent-height]
(defn skeleton [parent-height]
(let [number-of-skeletons (int (Math/floor (/ parent-height message-skeleton-height)))]
[rn/view {:style {:background-color (colors/theme-colors
colors/white
colors/neutral-90)
:flex 1}}
(for [n (range number-of-skeletons)]
[message-skeleton {:key n}])]))
[message-skeleton {:key n}])]))
+23 -1
View File
@@ -9,6 +9,28 @@
(def middle-dot "·")
(defn display-name [{:keys [profile-name nickname ens-name text-style]}]
(let [ens? (-> ens-name string/blank? not)
nickname? (-> nickname string/blank? not)]
(if ens?
[text/text (merge {:weight :semi-bold
:size :paragraph-2
:style (style/ens-text)}
text-style)
ens-name]
[:<>
(if nickname?
[text/text (merge {:weight :semi-bold
:size :paragraph-2
:style (style/nickname-text)}
text-style)
nickname]
[text/text (merge {:weight :semi-bold
:size :paragraph-2
:style (style/profile-name-text nickname?)}
text-style)
profile-name])])))
(defn author [{:keys [profile-name nickname chat-key ens-name time-str contact? verified? untrustworthy?]}]
[:f>
(fn []
@@ -63,4 +85,4 @@
[text/text {:monospace true
:size :paragraph-2
:style (style/time-text ens?)}
time-str]]))])
time-str]]))])
@@ -1,11 +1,11 @@
(ns quo2.components.messages.system-message
(:require [react-native.core :as rn]
[react-native.reanimated :as reanimated]
[quo2.theme :as theme]
[quo2.components.avatars.icon-avatar :as icon-avatar]
(:require [quo2.components.avatars.icon-avatar :as icon-avatar]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[utils.string :as utils]))
(def themes-landed {:pinned colors/primary-50-opa-5
@@ -60,19 +60,19 @@
:flex-direction :row}
[rn/view {:align-items :center
:flex-direction :row}
[sm-icon {:icon :main-icons/delete16
[sm-icon {:icon :main-icons/delete
:color :danger
:opacity 5}]
[text/text {:size :paragraph-2
:style {:color (get-color :text)
:margin-right 5}}
(or label (:message-deleted labels))]
(or (get labels label) label (:message-deleted labels))]
[sm-timestamp timestamp-str]]])
(defmethod sm-render :added [{:keys [state mentions timestamp-str labels]}]
[rn/view {:align-items :center
:flex-direction :row}
[sm-icon {:icon :main-icons/add-user16
[sm-icon {:icon :main-icons/add-user
:color :primary
:opacity (if (= state :landed) 0 5)}]
[sm-user-avatar (:image (first mentions))]
@@ -94,7 +94,7 @@
[rn/view {:flex-direction :row
:flex 1
:align-items :center}
[sm-icon {:icon :main-icons/pin16
[sm-icon {:icon :main-icons/pin
:color :primary
:opacity (if (= state :landed) 0 5)}]
[rn/view {:flex-direction :column
@@ -14,31 +14,35 @@
:customization-color customization-color}]))
(defn floating-shell-button
"[floating-shell-button dynamic-buttons style]
dynamic-buttons
{:button-type {:on-press on-press :count count}}"
[dynamic-buttons style opacity-anim pointer-anim]
[:f>
(fn []
(let [original-style (merge {:flex-direction :row
:margin-horizontal 12} style)
animated-style (reanimated/apply-animations-to-style
{:opacity opacity-anim
:pointer-events pointer-anim}
original-style)]
[reanimated/view {:style animated-style}
;; Left Section
[rn/view {:style {:flex 1}}
[dynamic-button-view :search dynamic-buttons {:position :absolute
:right 8}]]
;; Mid Section (jump-to)
[dynamic-button-view :jump-to dynamic-buttons nil]
"[floating-shell-button dynamic-buttons style opacity-anim pointer-anim]
dynamic-buttons {:button-type {:on-press on-press :count count}}
style override style
opacity-anim reanimated value (optional)"
([dynamic-button style]
(floating-shell-button dynamic-button style nil))
([dynamic-buttons style opacity-anim]
[:f>
(fn []
(let [original-style (merge {:flex-direction :row
:margin-horizontal 12
:pointer-events :box-none} style)
animated-style (reanimated/apply-animations-to-style
(if opacity-anim
{:opacity opacity-anim} {})
original-style)]
[reanimated/view {:style animated-style}
;; Left Section
[rn/view {:style {:flex 1}}
[dynamic-button-view :search dynamic-buttons {:position :absolute
:right 8}]]
;; Mid Section (jump-to)
[dynamic-button-view :jump-to dynamic-buttons nil]
;; Right Section
[rn/view {:style {:flex 1}}
[rn/view {:style {:position :absolute
:flex-direction :row
:right 0}}
[dynamic-button-view :mention dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-down dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-up dynamic-buttons {:margin-left 8}]
[dynamic-button-view :bottom dynamic-buttons {:margin-left 8}]]]]))])
[rn/view {:style {:flex 1}}
[rn/view {:style {:position :absolute
:flex-direction :row
:right 0}}
[dynamic-button-view :mention dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-down dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-up dynamic-buttons {:margin-left 8}]
[dynamic-button-view :bottom dynamic-buttons {:margin-left 8}]]]]))]))
+105 -105
View File
@@ -3,7 +3,9 @@
[quo2.foundations.colors :as colors]
[quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[clojure.string :as string]))
[clojure.string :as string]
[quo2.components.buttons.button :as button]
[quo2.components.avatars.user-avatar :as user-avatar]))
(def ^:private centrify-style
{:display :flex
@@ -12,12 +14,10 @@
(def ^:private align-left (assoc centrify-style :align-items :flex-start))
(def ^:private icon-styles (assoc centrify-style :width 32 :height 32 :border-radius 10))
(defn- big? [size] (= size :big))
(defn- icon-props [color size]
(merge {:size 20
(merge {:size 20
:container-style {:width (if (big? size)
20
16)
@@ -28,34 +28,35 @@
{:color color}
{:no-color true})))
(defn render-left-section [{:keys [on-press icon
icon-color icon-background-color]}
put-middle-section-on-left?]
[rn/view {:style (merge
icon-styles
{:background-color icon-background-color
:width 32
:height 32}
(when put-middle-section-on-left? {:margin-right 5}))}
[rn/touchable-opacity {:on-press on-press}
[icons/icon icon (icon-props icon-color :big)]]])
(defn left-section-view [{:keys [on-press icon accessibility-label type] :or {type :grey}}
put-middle-section-on-left?]
[rn/view {:style (when put-middle-section-on-left? {:margin-right 5})}
[button/button {:on-press on-press
:icon true
:type type
:size 32
:accessibility-label accessibility-label}
icon]])
(defn- mid-section-comp
[{:keys [description-user-icon horizontal-description? text-secondary-color align-mid? text-color icon main-text type description]}]
[rn/view {:style (assoc
centrify-style
:flex-direction :row
:margin-horizontal 2)}
[{:keys [description-img description-user-icon horizontal-description?
text-secondary-color align-mid? text-color icon main-text type description]}]
[rn/view {:style (assoc centrify-style
:flex-direction :row
:margin-horizontal 2)}
(when (or (and (not horizontal-description?)
align-mid?
(not= :text-with-description type))
(and description-user-icon
(and (or description-img description-user-icon)
(not icon)))
[rn/image {:source {:uri description-user-icon}
:style {:width 32
:height 32
:border-radius 32
:margin-right 8}}])
(if description-img
[rn/view {:margin-right 8}
[description-img]]
[rn/image {:source {:uri description-user-icon}
:style {:width 32
:height 32
:border-radius 32
:margin-right 8}}]))
[rn/view {:style {:flex-direction (if horizontal-description?
:row
:column)}}
@@ -73,76 +74,69 @@
horizontal-description? (assoc :margin-left 4 :margin-top 2))}
description])]])
(defn- render-mid-section
(defn- mid-section-view
[{:keys [horizontal-description? one-icon-align-left? type left-align?
main-text right-icon main-text-icon-color left-icon] :as props}]
(let [text-color (if (colors/dark?) colors/neutral-5 colors/neutral-95)
text-secondary-color (if (colors/dark?) colors/neutral-40 colors/neutral-50)
main-text right-icon main-text-icon-color left-icon on-press avatar] :as props}]
(let [text-color (if (colors/dark?) colors/neutral-5 colors/neutral-95)
text-secondary-color (if (colors/dark?) colors/neutral-40 colors/neutral-50)
component-instance [mid-section-comp (assoc props :text-secondary-color text-secondary-color)]]
[rn/view {:style (merge
(if left-align?
align-left
centrify-style)
{:flex 1
:margin-left 4
:text-align-vertical :center})}
(case type
:text-only [text/text {:size :paragraph-1
:weight :semi-bold
:style {:color text-color}}
main-text]
:text-with-two-icons [rn/view {:style (assoc centrify-style :flex-direction :row)}
[icons/icon left-icon
(icon-props main-text-icon-color :big)]
[text/text {:size :paragraph-1
:weight :semi-bold
:style {:padding-horizontal 4
:color text-color}}
main-text]
[rn/touchable-opacity {:on-press on-press}
[rn/view {:style (merge
(if left-align?
align-left
centrify-style)
{:flex 1
:margin-left 4
:text-align-vertical :center})}
(case type
:text-only [text/text {:size :paragraph-1
:weight :semi-bold
:style {:color text-color}}
main-text]
:user-avatar [rn/view {:style (assoc centrify-style :flex-direction :row)}
[user-avatar/user-avatar avatar]
[text/text {:size :paragraph-1
:weight :semi-bold
:style {:padding-horizontal 4
:color text-color}}
main-text]]
:text-with-two-icons [rn/view {:style (assoc centrify-style :flex-direction :row)}
[icons/icon left-icon
(icon-props main-text-icon-color :big)]
[text/text {:size :paragraph-1
:weight :semi-bold
:style {:padding-horizontal 4
:color text-color}}
main-text]
[icons/icon right-icon
(icon-props main-text-icon-color :big)]]
:text-with-one-icon [rn/view {:style {:flex-direction :row}}
(if one-icon-align-left?
[rn/view {:style {:flex-direction :row
:align-items :center}}
(when horizontal-description?
[icons/icon left-icon
(icon-props main-text-icon-color :big)])
component-instance]
[rn/view {:style {:flex-direction :row
:align-items :center}}
component-instance
(when horizontal-description?
[icons/icon left-icon
(icon-props main-text-icon-color :big)])])]
:text-with-description component-instance)]]))
[icons/icon right-icon
(icon-props main-text-icon-color :big)]]
:text-with-one-icon [rn/view {:style {:flex-direction :row}}
(if one-icon-align-left?
[rn/view {:style {:flex-direction :row
:align-items :center}}
(when horizontal-description?
[icons/icon left-icon
(icon-props main-text-icon-color :big)])
component-instance]
[rn/view {:style {:flex-direction :row
:align-items :center}}
component-instance
(when horizontal-description?
[icons/icon left-icon
(icon-props main-text-icon-color :big)])])]
:text-with-description component-instance)]))
(defn- right-section-icon
[{:keys [background-color icon icon-color push-to-the-left?] :or {push-to-the-left? false}}]
[rn/view {:style (assoc
icon-styles
:background-color background-color
:width 32
:height 32
:margin-right (if push-to-the-left? 8 0))}
[icons/icon icon (icon-props icon-color :big)]])
(defn- render-right-section [right-section-buttons]
[rn/view {:style (assoc
centrify-style
:flex-direction :row
:flex 1
:justify-content :flex-end)}
(defn- right-section-view [right-section-buttons]
[rn/view {:style (assoc centrify-style
:flex-direction :row
:flex 1
:justify-content :flex-end)}
(let [last-icon-index (-> right-section-buttons count dec)]
(map-indexed (fn [index {:keys [icon background-color icon-color on-press]}]
(map-indexed (fn [index {:keys [icon on-press type] :or {type :grey}}]
^{:key index}
[rn/touchable-opacity {:on-press on-press}
[right-section-icon
{:icon icon
:background-color background-color
:icon-color icon-color
:push-to-the-left? (not= index last-icon-index)}]])
[rn/view {:style {:margin-right (if (not= index last-icon-index) 8 0)}}
[button/button {:on-press on-press :icon true :type type :size 32}
icon]])
right-section-buttons))])
(defn page-nav
@@ -154,7 +148,7 @@
:page-nav-color color
:page-nav-background-uri image-uri
:mid-section
{:type one-of :text-only :text-with-two-icons :text-with-one-icon :text-with-description
{:type one-of :text-only :text-with-two-icons :text-with-one-icon :text-with-description :user-avatar
:icon icon
:main-text string
:left-icon icon
@@ -163,16 +157,19 @@
:description-color color
:description-icon icon
:description-user-icon icon
:description-img a render prop which will be used in place of :description-user-icon
:main-text-icon-color color
}
:left-section
{:on-press event
{:type button-type
:on-press event
:icon icon
:icon-color color
:icon-background-color color
}
:right-section-buttons vector of
{:on-press event
{:type button-type
:on-press event
:icon icon
:icon-color color
:icon-background-color color
@@ -189,32 +186,35 @@
mid-section-props
{:type (:type mid-section)
:horizontal-description? horizontal-description?
:description-img (:description-img mid-section)
:main-text (:main-text mid-section)
:main-text-icon-color (:main-text-icon-color mid-section)
:one-icon-align-left? one-icon-align-left?
:right-icon (:right-icon mid-section)
:icon (:icon mid-section)
:left-icon (:left-icon mid-section)}]
:left-icon (:left-icon mid-section)
:avatar (:avatar mid-section)}]
[rn/view {:style (cond-> {:display :flex
:flex-direction :row
;; iPhone 11 Pro's height in Figma divided by Component height 56/1125
:align-items :center
:padding-horizontal 20
:height 56
:justify-content :space-between}
page-nav-background-uri (assoc :background-color page-nav-color)
page-nav-color (assoc :background page-nav-background-uri))}
[rn/view {:style {:flex 1
:flex-direction :row
:align-items :center}}
(render-left-section left-section put-middle-section-on-left?)
[left-section-view left-section put-middle-section-on-left?]
(when put-middle-section-on-left?
[render-mid-section (assoc mid-section-props
:left-align? true
:description (:description mid-section)
:description-color (:description-color mid-section)
:description-icon (:description-icon mid-section)
:align-mid? align-mid?
:description-user-icon (:description-user-icon mid-section))])]
[mid-section-view (assoc mid-section-props
:left-align? true
:description (:description mid-section)
:description-color (:description-color mid-section)
:description-icon (:description-icon mid-section)
:align-mid? align-mid?
:description-user-icon (:description-user-icon mid-section))])]
(when-not put-middle-section-on-left?
[render-mid-section mid-section-props])
(render-right-section right-section-buttons)]))
[mid-section-view mid-section-props])
[right-section-view right-section-buttons]]))
@@ -1,99 +0,0 @@
(ns quo2.components.navigation.top-nav
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
[quo2.components.counter.counter :as counter]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.avatars.user-avatar :as user-avatar]
[react-native.hole-view :as hole-view]))
(defn- get-button-common-props [type]
(let [default? (= type :default)
dark? (colors/dark?)]
{:icon true
:size 32
:style {:margin-left 12}
:type (if default?
(if dark? :grey :dark-grey)
type)
:override-background-color (when (and dark? default?)
colors/neutral-90)}))
(defn- base-button [icon on-press accessibility-label button-common-props]
[quo2.button/button
(merge
{:on-press on-press
:accessibility-label accessibility-label}
button-common-props)
icon])
(defn top-nav
"[top-nav opts]
opts
{:type :default/:blurred/:shell
:new-notifications? true/false
:notification-indicator :unread-dot/:counter
:open-profile fn
:open-search fn
:open-scanner fn
:show-qr fn
:open-activity-center fn
:style override-style
:avatar user-avatar
:counter-label number}
"
[{:keys [type new-notifications? notification-indicator open-profile open-search
open-scanner show-qr open-activity-center style avatar counter-label]}]
(let [button-common-props (get-button-common-props type)]
[rn/view {:style (merge
{:height 56}
style)}
;; Left Section
[rn/touchable-without-feedback {:on-press open-profile}
[rn/view {:style {:position :absolute
:left 20
:top 12}}
[user-avatar/user-avatar
(merge
{:ring? true
:status-indicator? true
:size :small}
avatar)]]]
;; Right Section
[rn/view {:style {:position :absolute
:right 20
:top 12
:flex-direction :row}}
;; TODO component shouldn't know anything about parent system, we should pass buttons as parameters
[base-button :i/search open-search :open-search-button button-common-props]
[base-button :i/scan open-scanner :open-scanner-button button-common-props]
[base-button :i/qr-code show-qr :show-qr-button button-common-props]
[rn/view ;; Keep view instead of "[:<>" to make sure relative
;; position is calculated from this view instead of its parent
[hole-view/hole-view {:key new-notifications? ;; Key is required to force removal of holes
:holes (cond
(not new-notifications?) ;; No new notifications, remove holes
[]
(= notification-indicator :unread-dot)
[{:x 37 :y -3 :width 10 :height 10 :borderRadius 5}]
:else
[{:x 33 :y -7 :width 18 :height 18 :borderRadius 7}])}
[base-button :i/activity-center open-activity-center
:open-activity-center-button button-common-props]]
(when new-notifications?
(if (= notification-indicator :counter)
[counter/counter {:outline false
:override-text-color colors/white
:override-bg-color colors/primary-50
:style {:position :absolute
:left 34
:top -6}}
counter-label]
[rn/view {:style {:width 8
:height 8
:border-radius 4
:top -2
:left 38
:position :absolute
:background-color colors/primary-50}}]))]]]))
@@ -64,7 +64,8 @@
(map (fn [s]
[rn/view {:style {:margin-right 4
:margin-top 0}}
[text/text {:size :paragraph-2}
[text/text {:size :paragraph-2
:style {:color colors/white}}
s]])
(string/split detail #"\s+"))
[[rn/view {:margin-right 4
@@ -10,6 +10,8 @@
:position :absolute
:right 22
:border-radius 6
:justify-content :center
:align-items :center
:background-color (colors/theme-colors colors/primary-50 colors/primary-60)}
style)}
[rn/text {:style (merge typography/font-medium typography/label {:color colors/white :text-align :center})} count]]))
@@ -0,0 +1,84 @@
(ns quo2.components.selectors.--tests--.selectors-component-spec
(:require ["@testing-library/react-native" :as rtl]
[quo2.components.selectors.selectors :as selectors]
[reagent.core :as reagent]))
(defn render-toggle
([]
(render-toggle {}))
([opts]
(rtl/render (reagent/as-element [selectors/toggle opts]))))
(defn render-checkbox
([]
(render-checkbox {}))
([opts]
(rtl/render (reagent/as-element [selectors/checkbox opts]))))
(defn render-checkbox-prefill
([]
(render-checkbox-prefill {}))
([opts]
(rtl/render (reagent/as-element [selectors/checkbox-prefill opts]))))
(defn render-radio
([]
(render-radio {}))
([opts]
(rtl/render (reagent/as-element [selectors/radio opts]))))
(js/global.test "default render of toggle component"
(fn []
(render-toggle)
(-> (js/expect (rtl/screen.getByTestId "toggle-component"))
(.toBeTruthy))))
(js/global.test "toggle component on change is working"
(let [mock-fn (js/jest.fn)]
(fn []
(render-toggle {:on-change mock-fn})
(rtl/fireEvent.press (rtl/screen.getByTestId "toggle-component"))
(-> (js/expect mock-fn)
(.toHaveBeenCalledTimes 1)))))
(js/global.test "default render of radio component"
(fn []
(render-radio)
(-> (js/expect (rtl/screen.getByTestId "radio-component"))
(.toBeTruthy))))
(js/global.test "radio component on change is working"
(let [mock-fn (js/jest.fn)]
(fn []
(render-radio {:on-change mock-fn})
(rtl/fireEvent.press (rtl/screen.getByTestId "radio-component"))
(-> (js/expect mock-fn)
(.toHaveBeenCalledTimes 1)))))
(js/global.test "default render of checkbox component"
(fn []
(render-checkbox)
(-> (js/expect (rtl/screen.getByTestId "checkbox-component"))
(.toBeTruthy))))
(js/global.test "checkbox component on change is working"
(let [mock-fn (js/jest.fn)]
(fn []
(render-checkbox {:on-change mock-fn})
(rtl/fireEvent.press (rtl/screen.getByTestId "checkbox-component"))
(-> (js/expect mock-fn)
(.toHaveBeenCalledTimes 1)))))
(js/global.test "default render of checkbox-prefill component"
(fn []
(render-checkbox-prefill)
(-> (js/expect (rtl/screen.getByTestId "checkbox-prefill-component"))
(.toBeTruthy))))
(js/global.test "checkbox-prefill component on change is working"
(let [mock-fn (js/jest.fn)]
(fn []
(render-checkbox-prefill {:on-change mock-fn})
(rtl/fireEvent.press (rtl/screen.getByTestId "checkbox-prefill-component"))
(-> (js/expect mock-fn)
(.toHaveBeenCalledTimes 1)))))
+8 -4
View File
@@ -43,7 +43,8 @@
(colors/alpha colors/neutral-20 (if disabled? 0.3 1))
(colors/alpha colors/neutral-70 (if disabled? 0.3 1))))})
:accessibility-label (str "checkbox-" (if @checked? "on" "off"))
:accessibility-role :checkbox}
:accessibility-role :checkbox
:testID "checkbox-prefill-component"}
(when @checked?
[rn/view {:style
{:height 20
@@ -82,7 +83,8 @@
:border-color (if @checked? :none
(get-color @checked? disabled? blurred-background?))}
:accessibility-label (str "checkbox-" (if @checked? "on" "off"))
:accessibility-role :checkbox}
:accessibility-role :checkbox
:testID "checkbox-component"}
(when @checked?
[rn/view {:style
{:height 20
@@ -108,7 +110,8 @@
(colors/theme-colors colors/white
(colors/alpha colors/neutral-80 0.4)))})
:accessibility-label (str "radio-" (if @checked? "on" "off"))
:accessibility-role :checkbox}
:accessibility-role :checkbox
:testID "radio-component"}
[rn/view {:style
{:margin-left :auto
@@ -133,7 +136,8 @@
:border-radius 20
:background-color (get-color @checked? disabled? blurred-background?)})
:accessibility-label (str "toggle-" (if @checked? "on" "off"))
:accessibility-role :checkbox}
:accessibility-role :checkbox
:testID "toggle-component"}
[rn/view {:style
{:margin-left (if @checked? 12 2)
:height 16
+17 -14
View File
@@ -12,26 +12,29 @@
(def default-tab-size 32)
(defn indicator []
[rn/view {:position :absolute
:z-index 1
:right -2
:top -2
:width 10
:height 10
:border-radius 5
:justify-content :center
:align-items :center
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
[notification-dot]])
(defn tabs [{:keys [default-active on-change style]}]
(let [active-tab-id (reagent/atom default-active)]
(fn [{:keys [data size] :or {size default-tab-size}}]
[rn/view (merge {:flex-direction :row} style)
(doall
(for [{:keys [label id new-info accessibility-label]} data]
(for [{:keys [label id notification-dot? accessibility-label]} data]
^{:key id}
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
(when new-info
[rn/view {:position :absolute
:z-index 1
:right -2
:top -2
:width 10
:height 10
:border-radius 5
:justify-content :center
:align-items :center
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
[notification-dot]])
(when notification-dot?
[indicator])
[tab/tab
{:id id
:size size
@@ -155,7 +158,7 @@
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex @flat-list-ref
(.scrollToIndex ^js @flat-list-ref
#js {:animated true
:index index
:viewPosition 0.5}))
+3 -2
View File
@@ -104,11 +104,12 @@
(defn tag
[_ _]
(fn [{:keys [locked tokens size background-color]
(fn [{:keys [locked tokens size background-color on-press]
:or {size 24}}]
[base-tag/base-tag {:background-color background-color
:size size
:type :permission}
:type :permission
:on-press on-press}
[rn/view {:flex-direction :row
:align-items :center
:justify-content :flex-end}
+10 -3
View File
@@ -15,8 +15,10 @@
quo2.components.community.community-list-view
quo2.components.community.community-view
quo2.components.community.discover-card
quo2.components.community.token-gating
quo2.components.counter.counter
quo2.components.dividers.divider-label
quo2.components.dividers.date
quo2.components.dividers.new-messages
quo2.components.drawers.action-drawers
quo2.components.dropdowns.dropdown
@@ -35,11 +37,12 @@
quo2.components.tags.context-tags
quo2.components.tabs.tabs
quo2.components.tabs.account-selector
quo2.components.navigation.top-nav
quo2.components.navigation.floating-shell-button
quo2.components.tags.status-tags
quo2.components.navigation.page-nav
quo2.components.selectors.disclaimer))
quo2.components.selectors.disclaimer
quo2.components.selectors.selectors
quo2.components.loaders.skeleton))
(def button quo2.components.buttons.button/button)
(def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button)
@@ -58,14 +61,16 @@
(def tags quo2.components.tags.tags/tags)
(def user-avatar-tag quo2.components.tags.context-tags/user-avatar-tag)
(def context-tag quo2.components.tags.context-tags/context-tag)
(def group-avatar-tag quo2.components.tags.context-tags/group-avatar-tag)
(def tabs quo2.components.tabs.tabs/tabs)
(def scrollable-tabs quo2.components.tabs.tabs/scrollable-tabs)
(def account-selector quo2.components.tabs.account-selector/account-selector)
(def top-nav quo2.components.navigation.top-nav/top-nav)
(def floating-shell-button quo2.components.navigation.floating-shell-button/floating-shell-button)
(def status-tag quo2.components.tags.status-tags/status-tag)
(def page-nav quo2.components.navigation.page-nav/page-nav)
(def disclaimer quo2.components.selectors.disclaimer/disclaimer)
(def checkbox quo2.components.selectors.selectors/checkbox)
(def skeleton quo2.components.loaders.skeleton/skeleton)
;;;; AVATAR
(def account-avatar quo2.components.avatars.account-avatar/account-avatar)
@@ -85,10 +90,12 @@
(def community-title quo2.components.community.community-view/community-title)
(def permission-tag-container quo2.components.community.community-view/permission-tag-container)
(def discover-card quo2.components.community.discover-card/discover-card)
(def token-gating quo2.components.community.token-gating/token-gating)
;;;; DIVIDERS
(def divider-label quo2.components.dividers.divider-label/divider-label)
(def new-messages quo2.components.dividers.new-messages/new-messages)
(def divider-date quo2.components.dividers.date/date)
;;;; LIST ITEMS
(def channel-list-item quo2.components.list-items.channel/list-item)
+4
View File
@@ -0,0 +1,4 @@
(ns quo2.core-spec
(:require [quo2.components.selectors.--tests--.selectors-component-spec]
[quo2.components.counter.--tests--.counter-component-spec]
[quo2.components.drawers.--tests--.action-drawers-component-spec]))
+18 -1
View File
@@ -1,10 +1,13 @@
(ns react-native.core
(:require [reagent.core :as reagent]
["react-native" :as react-native]
["@react-native-community/blur" :as blur]
[react-native.flat-list :as flat-list]
[react-native.section-list :as section-list]))
[react-native.section-list :as section-list]
[react-native.platform :as platform]))
(def app-state ^js (.-AppState ^js react-native))
(def blur-view (reagent/adapt-react-class (.-BlurView blur)))
(def view (reagent/adapt-react-class (.-View ^js react-native)))
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js react-native)))
@@ -53,3 +56,17 @@
(js->clj (.get (.-Dimensions ^js react-native) "window") :keywordize-keys true))
(def status-bar (.-StatusBar ^js react-native))
(defn hw-back-add-listener [callback]
(.addEventListener (.-BackHandler ^js react-native) "hardwareBackPress" callback))
(defn hw-back-remove-listener [callback]
(.removeEventListener (.-BackHandler ^js react-native) "hardwareBackPress" callback))
(def keyboard-avoiding-view-class (reagent/adapt-react-class (.-KeyboardAvoidingView react-native)))
(defn keyboard-avoiding-view [props & children]
(into [keyboard-avoiding-view-class
(merge (when platform/ios? {:behavior :padding})
props)]
children))
+4
View File
@@ -5,5 +5,9 @@
(def os (when platform (.-OS ^js platform)))
(def version (when platform (.-Version ^js platform)))
(def android? (= os "android"))
(def ios? (= os "ios"))
(def low-device? (and android? (< version 29)))
+1 -1
View File
@@ -7,7 +7,7 @@
(defn consumer [component]
[consumer-raw
(fn [insets]
(fn [^js insets]
(reagent/as-element
[component (js->clj insets :keywordize-keys true)]))])
@@ -1,442 +0,0 @@
(ns status-im.activity-center.core-test
(:require [cljs.test :refer [deftest is testing]]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
[status-im.activity-center.notification-types :as types]
[status-im.constants :as constants]
[status-im.ethereum.json-rpc :as json-rpc]
status-im.events
[status-im.test-helpers :as h]
[status-im2.setup.config :as config]))
(defn setup []
(h/register-helper-events)
(rf/dispatch [:setup/app-started]))
;;;; Contact verification
(def notification-id 24)
(def contact-verification-rpc-response
{:activityCenterNotifications
[{:accepted false
:author "0x04d03f"
:chatId "0x04d03f"
:contactVerificationStatus constants/contact-verification-status-pending
:dismissed false
:id notification-id
:message {}
:name "0x04d03f"
:read true
:timestamp 1666647286000
:type types/contact-verification}]})
(def contact-verification-expected-notification
{:accepted false
:author "0x04d03f"
:chat-id "0x04d03f"
:contact-verification-status constants/contact-verification-status-pending
:dismissed false
:id notification-id
:last-message nil
:message {:command-parameters nil
:content {:chat-id nil
:ens-name nil
:image nil
:line-count nil
:links nil
:parsed-text nil
:response-to nil
:rtl? nil
:sticker nil
:text nil}
:outgoing false
:outgoing-status nil
:quoted-message nil}
:name "0x04d03f"
:read true
:reply-message nil
:timestamp 1666647286000
:type types/contact-verification})
(defn test-log-on-failure
[{:keys [notification-id event action]}]
(rf-test/run-test-sync
(setup)
(h/using-log-test-appender
(fn [logs]
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(rf/dispatch event)
(is (= {:args [(str "Failed to " action)
{:notification-id notification-id
:error :fake-error}]
:level :warn}
(last @logs)))))))
(defn test-contact-verification-event
[{:keys [event expected-rpc-call]}]
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks ::json-rpc/call :on-success (constantly contact-verification-rpc-response))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch event)
(is (= {types/no-type
{:all {:data [contact-verification-expected-notification]}
:unread {:data []}}
types/contact-verification
{:all {:data [contact-verification-expected-notification]}
:unread {:data []}}}
(get-in (h/db) [:activity-center :notifications])))
(is (= expected-rpc-call
(-> @spy-queue
(get-in [0 :args 0])
(select-keys [:method :params])))))))
(deftest contact-verification-decline-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "declines notification and reconciles"
(test-contact-verification-event
{:event [:activity-center.contact-verification/decline notification-id]
:expected-rpc-call {:method "wakuext_declineContactVerificationRequest"
:params [notification-id]}}))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/decline notification-id]
:action :contact-verification/decline}))))
(deftest contact-verification-reply-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "sends reply and reconciles"
(let [reply "any answer"]
(test-contact-verification-event
{:event [:activity-center.contact-verification/reply notification-id reply]
:expected-rpc-call {:method "wakuext_acceptContactVerificationRequest"
:params [notification-id reply]}})))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/reply notification-id "any answer"]
:action :contact-verification/reply}))))
(deftest contact-verification-mark-as-trusted-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "marks notification as trusted and reconciles"
(test-contact-verification-event
{:event [:activity-center.contact-verification/mark-as-trusted notification-id]
:expected-rpc-call {:method "wakuext_verifiedTrusted"
:params [{:id notification-id}]}}))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/mark-as-trusted notification-id]
:action :contact-verification/mark-as-trusted}))))
(deftest contact-verification-mark-as-untrustworthy-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "marks notification as untrustworthy and reconciles"
(test-contact-verification-event
{:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
:expected-rpc-call {:method "wakuext_verifiedUntrustworthy"
:params [{:id notification-id}]}}))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
:action :contact-verification/mark-as-untrustworthy}))))
;;;; Notification reconciliation
(deftest notifications-reconcile-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "does nothing when there are no new notifications"
(rf-test/run-test-sync
(setup)
(let [notifications {types/one-to-one-chat
{:all {:cursor ""
:data [{:id "0x1"
:read true
:type types/one-to-one-chat}
{:id "0x2"
:read false
:type types/one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3"
:read false
:type types/one-to-one-chat}]}}
types/private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type types/private-group-chat}]}}}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
(rf/dispatch [:activity-center.notifications/reconcile nil])
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
(testing "removes dismissed or accepted notifications"
(rf-test/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
notif-2 {:id "0x2" :read false :type types/one-to-one-chat}
notif-3 {:id "0x3" :read false :type types/one-to-one-chat}
notif-4 {:id "0x4" :read false :type types/private-group-chat}
notif-5 {:id "0x5" :read true :type types/private-group-chat}
notif-6 {:id "0x6" :read false :type types/private-group-chat}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/one-to-one-chat
{:all {:cursor "" :data [notif-1 notif-2]}
:unread {:cursor "" :data [notif-3]}}
types/private-group-chat
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[(assoc notif-1 :dismissed true)
(assoc notif-3 :accepted true)
(assoc notif-4 :dismissed true)
notif-5]])
(is (= {types/no-type
{:all {:data [notif-5]}
:unread {:data []}}
types/one-to-one-chat
{:all {:cursor "" :data [notif-2]}
:unread {:cursor "" :data []}}
types/private-group-chat
{:all {:data [notif-5]}
:unread {:cursor "" :data [notif-6]}}}
(get-in (h/db) [:activity-center :notifications]))))))
(testing "replaces old notifications with newly arrived ones"
(rf-test/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
notif-4 {:id "0x4" :read false :type types/private-group-chat}
notif-6 {:id "0x6" :read false :type types/private-group-chat}
new-notif-1 (assoc notif-1 :last-message {})
new-notif-4 (assoc notif-4 :author "0xabc")]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/no-type
{:all {:cursor "" :data [notif-1]}
:unread {:cursor "" :data [notif-4 notif-6]}}
types/one-to-one-chat
{:all {:cursor "" :data [notif-1]}}
types/private-group-chat
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4 notif-6]])
(is (= {types/no-type
{:all {:cursor "" :data [notif-6 new-notif-4 new-notif-1]}
:unread {:cursor "" :data [notif-6 new-notif-4]}}
types/one-to-one-chat
{:all {:cursor "" :data [new-notif-1]}
:unread {:data []}}
types/private-group-chat
{:all {:data [notif-6 new-notif-4]}
:unread {:cursor "" :data [notif-6 new-notif-4]}}}
(get-in (h/db) [:activity-center :notifications]))))))
(testing "reconciles notifications that switched their read/unread status"
(rf-test/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
new-notif-1 (assoc notif-1 :read false)]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/one-to-one-chat
{:all {:cursor "" :data [notif-1]}}}])
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1]])
(is (= {types/no-type
{:all {:data [new-notif-1]}
:unread {:data [new-notif-1]}}
types/one-to-one-chat
{:all {:cursor "" :data [new-notif-1]}
:unread {:data [new-notif-1]}}}
(get-in (h/db) [:activity-center :notifications]))))))
;; Sorting by timestamp and ID is compatible with what the backend does when
;; returning paginated results.
(testing "sorts notifications by timestamp and id in descending order"
(rf-test/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat :timestamp 1}
notif-2 {:id "0x2" :read true :type types/one-to-one-chat :timestamp 1}
notif-3 {:id "0x3" :read false :type types/one-to-one-chat :timestamp 50}
notif-4 {:id "0x4" :read false :type types/one-to-one-chat :timestamp 100}
notif-5 {:id "0x5" :read false :type types/one-to-one-chat :timestamp 100}
new-notif-1 (assoc notif-1 :last-message {})
new-notif-4 (assoc notif-4 :last-message {})]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/one-to-one-chat
{:all {:cursor "" :data [notif-1 notif-2]}
:unread {:cursor "" :data [notif-3 notif-4 notif-5]}}}])
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4]])
(is (= {types/no-type
{:all {:data [new-notif-4 new-notif-1]}
:unread {:data [new-notif-4]}}
types/one-to-one-chat
{:all {:cursor "" :data [new-notif-4 notif-2 new-notif-1]}
:unread {:cursor "" :data [notif-5 new-notif-4 notif-3]}}}
(get-in (h/db) [:activity-center :notifications]))))))))
;;;; Notifications fetching and pagination
(deftest notifications-fetch-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "fetches first page"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor "10"
:notifications [{:id "0x1"
:type types/one-to-one-chat
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-type types/one-to-one-chat}])
(is (= :unread (get-in (h/db) [:activity-center :filter :status])))
(is (= "" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with empty cursor when fetching first page")
(is (= {types/one-to-one-chat
{:unread {:cursor "10"
:data [{:chat-id "0x9"
:chat-name nil
:chat-type types/one-to-one-chat
:group-chat false
:id "0x1"
:public? false
:last-message nil
:message nil
:read false
:reply-message nil
:type types/one-to-one-chat}]}}}
(get-in (h/db) [:activity-center :notifications]))))))
(testing "does not fetch next page when pagination cursor reached the end"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
types/one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
;; The cursor can be nil sometimes because the reconciliation doesn't care
;; about updating the cursor value, but we have to make sure the next page is
;; only fetched if the current cursor is valid.
(testing "does not fetch next page when cursor is nil"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
types/one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :unread :cursor]
nil])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
(testing "fetches next page when pagination cursor is not empty"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:id "0x1"
:type types/mention
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
types/mention])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/mention :unread :cursor]
"10"])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= "wakuext_activityCenterNotificationsBy" (get-in @spy-queue [0 :args 0 :method])))
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with current cursor")
(is (= {types/mention
{:unread {:cursor ""
:data [{:chat-id "0x9"
:chat-name nil
:chat-type 3
:id "0x1"
:last-message nil
:message nil
:read false
:reply-message nil
:type types/mention}]}}}
(get-in (h/db) [:activity-center :notifications]))))))
(testing "does not fetch next page while it is still loading"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:all])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
types/one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :all :cursor]
"10"])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :all :loading?]
true])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
(testing "resets loading flag after an error"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
types/one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(is (nil? (get-in (h/db) [:activity-center :notifications types/one-to-one-chat :unread :loading?])))
(is (= [:activity-center.notifications/fetch-error
types/one-to-one-chat
:unread
:fake-error]
(:args (last @spy-queue)))))))))
+6 -6
View File
@@ -6,7 +6,7 @@
[status-im.add-new.db :as db]
[status-im.utils.random :as random]
[status-im.utils.utils :as utils]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[status-im.chat.models :as chat]
[status-im.i18n.i18n :as i18n]
[status-im.contact.core :as contact]
@@ -24,7 +24,7 @@
;;NOTE we want to handle only last resolve
(def resolve-last-id (atom nil))
(fx/defn new-chat-set-new-identity
(rf/defn new-chat-set-new-identity
{:events [:new-chat/set-new-identity]}
[{db :db} new-identity-raw new-ens-name id]
(let [ens-error (and (= new-identity-raw "0x") (not (string/blank? new-ens-name)))]
@@ -61,12 +61,12 @@
new-identity
@resolve-last-id])}})))))))
(fx/defn clear-new-identity
(rf/defn clear-new-identity
{:events [::clear-new-identity ::new-chat-focus]}
[{:keys [db]}]
{:db (dissoc db :contacts/new-identity)})
(fx/defn qr-code-handled
(rf/defn qr-code-handled
{:events [::qr-code-handled]}
[{:keys [db] :as cofx} {:keys [type public-key chat-id data ens-name]} {:keys [new-contact? nickname] :as opts}]
(let [public-key? (and (string? data)
@@ -79,7 +79,7 @@
validation-result (db/validate-pub-key db chat-key)]
(if-not validation-result
(if new-contact?
(fx/merge cofx
(rf/merge cofx
(contact/add-contact chat-key nickname ens-name)
(navigation/navigate-to-cofx :contacts-list {}))
(chat/start-chat cofx chat-key ens-name))
@@ -91,7 +91,7 @@
(i18n/label :t/can-not-add-yourself))
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :chat-stack])}})))
(fx/defn qr-code-scanned
(rf/defn qr-code-scanned
{:events [:contact/qr-code-scanned]}
[{:keys [db]} data opts]
{::router/handle-uri {:chain (ethereum/chain-keyword db)
+2 -2
View File
@@ -1,6 +1,6 @@
(ns status-im.async-storage.transit
(:require [cognitect.transit :as transit]
[taoensso.timbre :as timbre]))
[taoensso.timbre :as log]))
(def reader (transit/reader :json))
(def writer (transit/writer :json))
@@ -8,4 +8,4 @@
(defn clj->transit [o] (transit/write writer o))
(defn transit->clj [o] (try (transit/read reader o)
(catch :default e
(timbre/error e))))
(log/error e))))
+4 -4
View File
@@ -2,19 +2,19 @@
(:require [re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.fx :as fx]))
[utils.re-frame :as rf]))
(fx/defn handle-backup-failed
(rf/defn handle-backup-failed
{:events [::backup-failed]}
[{:keys [db]}]
{:db (dissoc db :backup/performing-backup)})
(fx/defn handle-backup-perfomed
(rf/defn handle-backup-perfomed
{:events [::backup-performed]}
[{:keys [db]}]
{:db (dissoc db :backup/performing-backup)})
(fx/defn handle-perform-backup-pressed
(rf/defn handle-perform-backup-pressed
{:events [:multiaccounts.ui/perform-backup-pressed]}
[{:keys [db]}]
{:db (assoc db :backup/performing-backup true)
+14 -14
View File
@@ -4,7 +4,7 @@
[status-im.i18n.i18n :as i18n]
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im2.navigation.events :as navigation]
[status-im.utils.fx :as fx]))
[utils.re-frame :as rf]))
(def address-regex #"enode://[a-zA-Z0-9]+:?(.*)\@\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b:(\d{1,5})")
@@ -17,11 +17,11 @@
:id (string/replace id "-" "")
:name bootnode-name})
(fx/defn fetch [cofx id]
(rf/defn fetch [cofx id]
(let [network (get-in cofx [:db :networks/current-network])]
(get-in cofx [:db :multiaccount :custom-bootnodes network id])))
(fx/defn set-input
(rf/defn set-input
{:events [:bootnodes.ui/input-changed]}
[{:keys [db]} input-key value]
{:db (update
@@ -35,13 +35,13 @@
:name (string/blank? value)
:url (not (valid-address? value)))})})
(fx/defn edit
(rf/defn edit
{:events [:bootnodes.ui/add-bootnode-pressed]}
[{:keys [db] :as cofx} id]
(let [{:keys [id
address
name]} (fetch cofx id)
fxs (fx/merge cofx
fxs (rf/merge cofx
(set-input :id id)
(set-input :url (str address))
(set-input :name (str name)))]
@@ -52,17 +52,17 @@
(let [network (:networks/current-network db)]
(get-in db [:multiaccount :custom-bootnodes-enabled? network])))
(fx/defn delete [{{:keys [multiaccount] :as db} :db :as cofx} id]
(rf/defn delete [{{:keys [multiaccount] :as db} :db :as cofx} id]
(let [network (:networks/current-network db)
new-multiaccount (update-in multiaccount [:custom-bootnodes network] dissoc id)]
(fx/merge cofx
(rf/merge cofx
{:db (assoc db :multiaccount new-multiaccount)}
(multiaccounts.update/multiaccount-update
:custom-bootnodes (:custom-bootnodes new-multiaccount)
{:success-event (when (custom-bootnodes-in-use? cofx)
[:multiaccounts.update.callback/save-settings-success])}))))
(fx/defn upsert
(rf/defn upsert
{:events [:bootnodes.ui/save-pressed]
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
[{{:bootnodes/keys [manage] :keys [multiaccount] :as db} :db
@@ -79,7 +79,7 @@
[network (:id bootnode)]
bootnode)]
(fx/merge cofx
(rf/merge cofx
{:db (dissoc db :bootnodes/manage)
:dispatch [:navigate-back]}
(multiaccounts.update/multiaccount-update
@@ -87,7 +87,7 @@
{:success-event (when (custom-bootnodes-in-use? cofx)
[:multiaccounts.update.callback/save-settings-success])}))))
(fx/defn toggle-custom-bootnodes
(rf/defn toggle-custom-bootnodes
{:events [:bootnodes.ui/custom-bootnodes-switch-toggled]}
[{:keys [db] :as cofx} value]
(let [current-network (:networks/current-network db)
@@ -97,13 +97,13 @@
:custom-bootnodes-enabled? (assoc bootnodes-settings current-network value)
{:success-event [:multiaccounts.update.callback/save-settings-success]})))
(fx/defn set-bootnodes-from-qr
(rf/defn set-bootnodes-from-qr
{:events [:bootnodes.callback/qr-code-scanned]}
[cofx url]
(assoc (set-input cofx :url (string/trim url))
:dispatch [:navigate-back]))
(fx/defn show-delete-bootnode-confirmation
(rf/defn show-delete-bootnode-confirmation
{:events [:bootnodes.ui/delete-pressed]}
[_ bootnode-id]
{:ui/show-confirmation {:title (i18n/label :t/delete-bootnode-title)
@@ -111,9 +111,9 @@
:confirm-button-text (i18n/label :t/delete-bootnode)
:on-accept #(re-frame/dispatch [:bootnodes.ui/delete-confirmed bootnode-id])}})
(fx/defn delete-bootnode
(rf/defn delete-bootnode
{:events [:bootnodes.ui/delete-confirmed]}
[cofx bootnode-id]
(fx/merge cofx
(rf/merge cofx
(delete bootnode-id)
(navigation/navigate-back)))
+5 -5
View File
@@ -1,7 +1,7 @@
(ns status-im.bottom-sheet.core
(:require [status-im.utils.fx :as fx]))
(:require [utils.re-frame :as rf]))
(fx/defn show-bottom-sheet
(rf/defn show-bottom-sheet
[{:keys [db]} {:keys [view options]}]
{:show-bottom-sheet nil
:db (assoc db
@@ -9,7 +9,7 @@
:bottom-sheet/view view
:bottom-sheet/options options)})
(fx/defn show-bottom-sheet-event
(rf/defn show-bottom-sheet-event
{:events [:bottom-sheet/show-sheet]}
[cofx view options]
(show-bottom-sheet
@@ -17,8 +17,8 @@
{:view view
:options options}))
(fx/defn hide-bottom-sheet
(rf/defn hide-bottom-sheet
{:events [:bottom-sheet/hide]}
[{:keys [db]}]
{:hide-bottom-sheet nil
:db (assoc db :bottom-sheet/show? false)})
:db (assoc db :bottom-sheet/show? false)})
+57 -57
View File
@@ -10,7 +10,7 @@
[status-im.native-module.core :as status]
[status-im.ui.components.list-selection :as list-selection]
[status-im2.navigation.events :as navigation]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[status-im.utils.http :as http]
[status-im.utils.platform :as platform]
[status-im.utils.random :as random]
@@ -22,15 +22,15 @@
[status-im.bottom-sheet.core :as bottom-sheet]
[status-im.browser.webview-ref :as webview-ref]
["eth-phishing-detect" :as eth-phishing-detect]
[status-im.utils.debounce :as debounce]
[utils.debounce :as debounce]
[status-im.browser.eip3085 :as eip3085]
[status-im.browser.eip3326 :as eip3326]))
(fx/defn update-browser-option
(rf/defn update-browser-option
[{:keys [db]} option-key option-value]
{:db (assoc-in db [:browser/options option-key] option-value)})
(fx/defn update-browser-options
(rf/defn update-browser-options
[{:keys [db]} options]
{:db (update db :browser/options merge options)})
@@ -48,7 +48,7 @@
(when url
(string/starts-with? url "https://")))))
(fx/defn remove-browser
(rf/defn remove-browser
{:events [:browser.ui/remove-browser-pressed]}
[{:keys [db]} browser-id]
{:db (update-in db [:browser/browsers] dissoc browser-id)
@@ -56,7 +56,7 @@
:params [browser-id]
:on-success #()}]})
(fx/defn clear-all-browsers
(rf/defn clear-all-browsers
{:events [:browser.ui/clear-all-browsers-pressed]}
[{:keys [db]}]
{:db (dissoc db :browser/browsers)
@@ -77,7 +77,7 @@
(re-frame/dispatch [:browser.callback/resolve-ens-multihash-success url])
(re-frame/dispatch [:browser.callback/resolve-ens-multihash-error])))
(fx/defn resolve-url
(rf/defn resolve-url
[{:keys [db]} {:keys [error? resolved-url]}]
(when (not error?)
(let [current-url (get-current-url (get-current-browser db))
@@ -89,7 +89,7 @@
:cb resolve-ens-contenthash-callback}}
{:db (update db :browser/options assoc :url (or resolved-url current-url) :resolving? false)}))))
(fx/defn update-browser
(rf/defn update-browser
[{:keys [db]}
{:keys [browser-id] :as browser}]
(let [updated-browser (-> browser
@@ -102,7 +102,7 @@
:params [(select-keys updated-browser [:browser-id :timestamp :name :dapp? :history :history-index])]
:on-success #()}]}))
(fx/defn store-bookmark
(rf/defn store-bookmark
{:events [:browser/store-bookmark]}
[{:keys [db]}
{:keys [url] :as bookmark}]
@@ -111,24 +111,24 @@
:params [bookmark]
:on-success #()}]})
(fx/defn update-bookmark
(rf/defn update-bookmark
{:events [:browser/update-bookmark]}
[{:keys [db] :as cofx}
{:keys [url] :as bookmark}]
(let [old-bookmark (get-in db [:bookmarks/bookmarks url])
new-bookmark (merge old-bookmark bookmark)]
(fx/merge cofx {:db (assoc-in db [:bookmarks/bookmarks url] new-bookmark)
(rf/merge cofx {:db (assoc-in db [:bookmarks/bookmarks url] new-bookmark)
::json-rpc/call [{:method "wakuext_updateBookmark"
:params [url bookmark]
:on-success #()}]})))
(fx/defn delete-bookmark
(rf/defn delete-bookmark
{:events [:browser/delete-bookmark]}
[{:keys [db] :as cofx}
url]
(let [old-bookmark (get-in db [:bookmarks/bookmarks url])
removed-bookmark (merge old-bookmark {:removed true})]
(fx/merge cofx {:db (update db :bookmarks/bookmarks dissoc url)
(rf/merge cofx {:db (update db :bookmarks/bookmarks dissoc url)
::json-rpc/call [{:method "wakuext_removeBookmark"
:params [url]
:on-success #()}]})))
@@ -136,16 +136,16 @@
(defn can-go-back? [{:keys [history-index]}]
(pos? history-index))
(fx/defn navigate-to-previous-page
(rf/defn navigate-to-previous-page
{:events [:browser.ui/previous-page-button-pressed]}
[cofx]
(let [{:keys [history-index] :as browser} (get-current-browser (:db cofx))]
(when (can-go-back? browser)
(fx/merge cofx
(rf/merge cofx
(update-browser (assoc browser :history-index (dec history-index)))
(resolve-url nil)))))
(fx/defn ignore-unsafe
(rf/defn ignore-unsafe
{:events [:browser/ignore-unsafe]}
[cofx]
(let [browser (get-current-browser (:db cofx))
@@ -155,16 +155,16 @@
(defn can-go-forward? [{:keys [history-index history]}]
(< history-index (dec (count history))))
(fx/defn navigate-to-next-page
(rf/defn navigate-to-next-page
{:events [:browser.ui/next-page-button-pressed]}
[cofx]
(let [{:keys [history-index] :as browser} (get-current-browser (:db cofx))]
(when (can-go-forward? browser)
(fx/merge cofx
(rf/merge cofx
(update-browser (assoc browser :history-index (inc history-index)))
(resolve-url nil)))))
(fx/defn update-browser-history
(rf/defn update-browser-history
[cofx browser url]
(let [history-index (:history-index browser)
history (:history browser)]
@@ -176,47 +176,47 @@
:history new-history
:history-index new-index))))))
(fx/defn resolve-ens-multihash-success
(rf/defn resolve-ens-multihash-success
{:events [:browser.callback/resolve-ens-multihash-success]}
[{:keys [db] :as cofx} url]
(let [current-url (get-current-url (get-current-browser db))
host (http/url-host current-url)
path (subs current-url (+ (.indexOf ^js current-url host) (count host)))
gateway url]
(fx/merge cofx
(rf/merge cofx
{:db (-> (update db :browser/options
assoc
:url (str gateway path)
:resolving? false)
(assoc-in [:browser/options :resolved-ens host] gateway))})))
(fx/defn resolve-ens-multihash-error
(rf/defn resolve-ens-multihash-error
{:events [:browser.callback/resolve-ens-multihash-error]}
[{:keys [db] :as cofx}]
(update-browser-options cofx {:url (get-current-url (get-current-browser db))
:resolving? false
:error? true}))
(fx/defn handle-browser-error
(rf/defn handle-browser-error
{:events [:browser/error-occured]}
[cofx]
(fx/merge cofx
(rf/merge cofx
(update-browser-option :error? true)
(update-browser-option :loading? false)))
(fx/defn handle-pdf
(rf/defn handle-pdf
[_ url]
(when (and platform/android? (string/ends-with? url ".pdf"))
{:browser/show-web-browser-selection url}))
(fx/defn handle-message-link
(rf/defn handle-message-link
{:events [:browser.ui/message-link-pressed]}
[_ link]
(if (links/universal-link? link)
{:dispatch [:universal-links/handle-url link]}
{:browser/show-browser-selection link}))
(fx/defn update-browser-on-nav-change
(rf/defn update-browser-on-nav-change
[cofx url error?]
(let [browser (get-current-browser (:db cofx))
options (get-in cofx [:db :browser/options])
@@ -228,18 +228,18 @@
resolved-url (if resolved-ens
(http/normalize-url (string/replace url (second resolved-ens) (first resolved-ens)))
url)]
(fx/merge cofx
(rf/merge cofx
(update-browser-history browser resolved-url)
(handle-pdf url)
(resolve-url {:error? error? :resolved-url (when resolved-ens url)}))))))
(fx/defn update-browser-name
(rf/defn update-browser-name
[cofx title]
(let [browser (get-current-browser (:db cofx))]
(when (and browser (not (:dapp? browser)) title (not (string/blank? title)))
(update-browser cofx (assoc browser :name title)))))
(fx/defn navigation-state-changed
(rf/defn navigation-state-changed
{:events [:browser/navigation-state-changed]}
[cofx event error?]
(let [{:strs [url loading title]} (js->clj event)
@@ -247,12 +247,12 @@
(if (links/universal-link? url)
(when-not (and deep-link? platform/ios?) ;; ios webview handles this
{:dispatch [:universal-links/handle-url url]})
(fx/merge cofx
(rf/merge cofx
(update-browser-option :loading? loading)
(update-browser-name title)
(update-browser-on-nav-change url error?)))))
(fx/defn open-url-in-current-browser
(rf/defn open-url-in-current-browser
"Opens a url in the current browser, which mean no new entry is added to the home page
and history of the current browser is updated so that the user can navigate back to the
origin url"
@@ -262,12 +262,12 @@
normalized-url (http/normalize-and-decode-url url)]
(if (links/universal-link? normalized-url)
{:dispatch [:universal-links/handle-url normalized-url]}
(fx/merge cofx
(rf/merge cofx
(update-browser-option :url-editing? false)
(update-browser-history browser normalized-url)
(resolve-url nil)))))
(fx/defn open-url
(rf/defn open-url
"Opens a url in the browser. If a host can be extracted from the url and
there is already a browser for this host, this browser is reused
If the browser is reused, the history is flushed"
@@ -279,7 +279,7 @@
:history [normalized-url]}]
(if (links/universal-link? normalized-url)
{:dispatch [:universal-links/handle-url normalized-url]}
(fx/merge cofx
(rf/merge cofx
{:db (assoc db :browser/options
{:browser-id (:browser-id browser)})}
(navigation/change-tab :browser)
@@ -287,19 +287,19 @@
(update-browser browser)
(resolve-url nil)))))
(fx/defn open-existing-browser
(rf/defn open-existing-browser
"Opens an existing browser with it's history"
{:events [:browser.ui/browser-item-selected]}
[{:keys [db] :as cofx} browser-id]
(let [browser (get-in db [:browser/browsers browser-id])]
(fx/merge cofx
(rf/merge cofx
{:db (assoc db :browser/options
{:browser-id browser-id})}
(update-browser browser)
(navigation/set-stack-root :browser-stack :browser)
(resolve-url nil))))
(fx/defn web3-error-callback
(rf/defn web3-error-callback
{:events [:browser.dapp/transaction-on-error]}
[_ message-id message]
{:browser/send-to-bridge
@@ -307,7 +307,7 @@
:messageId message-id
:error message}})
(fx/defn dapp-complete-transaction
(rf/defn dapp-complete-transaction
{:events [:browser.dapp/transaction-on-result]}
[_ message-id id result]
;;TODO check and test id
@@ -336,7 +336,7 @@
(ethereum/address? second-param)
[second-param (if typed? first-param (normalize-message first-param))]))))
(fx/defn send-to-bridge
(rf/defn send-to-bridge
{:events [:browser.callback/call-rpc]}
[_ message]
{:browser/send-to-bridge message})
@@ -346,7 +346,7 @@
constants/web3-personal-sign
constants/web3-eth-sign constants/web3-keycard-sign-typed-data} method))
(fx/defn web3-send-async
(rf/defn web3-send-async
[cofx dapp-name {:keys [method params id] :as payload} message-id]
(let [message? (web3-sign-message? method)
dapps-address (get-in cofx [:db :multiaccount :dapps-address])
@@ -401,7 +401,7 @@
:error %1
:result %2}])]}))))
(fx/defn handle-no-permissions [cofx {:keys [method id]} message-id]
(rf/defn handle-no-permissions [cofx {:keys [method id]} message-id]
(if (= method "eth_accounts")
;; eth_accounts returns empty array for compatibility with meta-mask
(send-to-bridge cofx
@@ -425,28 +425,28 @@
(and (permissioned-method method)
(not (some #{constants/dapp-permission-web3} (get-in permissions [dapp-name :permissions]))))))
(fx/defn web3-send-async-read-only
(rf/defn web3-send-async-read-only
[{:keys [db] :as cofx} dapp-name {:keys [method] :as payload} message-id]
(if (has-permissions? db dapp-name method)
(handle-no-permissions cofx payload message-id)
(web3-send-async cofx dapp-name payload message-id)))
(fx/defn handle-scanned-qr-code
(rf/defn handle-scanned-qr-code
{:events [:browser.bridge.callback/qr-code-scanned]}
[cofx data {{:keys [dapp-name permission message-id]} :data}]
(fx/merge (assoc-in cofx [:db :browser/options :yielding-control?] false)
(rf/merge (assoc-in cofx [:db :browser/options :yielding-control?] false)
(browser.permissions/send-response-to-bridge permission message-id true data)
(browser.permissions/process-next-permission dapp-name)
(navigation/navigate-back)))
(fx/defn handle-canceled-qr-code
(rf/defn handle-canceled-qr-code
{:events [:browser.bridge.callback/qr-code-canceled]}
[cofx {{:keys [dapp-name permission message-id]} :data}]
(fx/merge (assoc-in cofx [:db :browser/options :yielding-control?] false)
(rf/merge (assoc-in cofx [:db :browser/options :yielding-control?] false)
(browser.permissions/send-response-to-bridge permission message-id true nil)
(browser.permissions/process-next-permission dapp-name)))
(fx/defn process-bridge-message
(rf/defn process-bridge-message
{:events [:browser/bridge-message-received]}
[{:keys [db] :as cofx} message]
(let [browser (get-current-browser db)
@@ -513,10 +513,10 @@
message (i18n/label :t/share-dapp-text {:link link})]
(list-selection/open-share {:message message})))
(fx/defn dapps-account-selected
(rf/defn dapps-account-selected
{:events [:dapps-account-selected]}
[{:keys [db] :as cofx} address]
(fx/merge cofx
(rf/merge cofx
{:browser/clear-web-data nil}
(bottom-sheet/hide-bottom-sheet)
(browser.permissions/clear-dapps-permissions)
@@ -525,38 +525,38 @@
(merge (navigation/navigate-back %)
{:dispatch [:browser.ui/browser-item-selected (get-in db [:browser/options :browser-id])]}))))
(fx/defn open-empty-tab
(rf/defn open-empty-tab
{:events [:browser.ui/open-empty-tab]}
[cofx]
(debounce/clear :browser/navigation-state-changed)
(navigation/set-stack-root cofx :browser-stack :empty-tab))
(fx/defn url-input-pressed
(rf/defn url-input-pressed
{:events [:browser.ui/url-input-pressed]}
[cofx _]
(update-browser-option cofx :url-editing? true))
(fx/defn url-input-blured
(rf/defn url-input-blured
{:events [:browser.ui/url-input-blured]}
[cofx]
(update-browser-option cofx :url-editing? false))
(fx/defn lock-pressed
(rf/defn lock-pressed
{:events [:browser.ui/lock-pressed]}
[cofx secure?]
(update-browser-option cofx :show-tooltip (if secure? :secure :not-secure)))
(fx/defn close-tooltip-pressed
(rf/defn close-tooltip-pressed
{:events [:browser.ui/close-tooltip-pressed]}
[cofx]
(update-browser-option cofx :show-tooltip nil))
(fx/defn loading-started
(rf/defn loading-started
{:events [:browser/loading-started]}
[cofx]
(update-browser-options cofx {:error? false :loading? true}))
(fx/defn handle-bookmarks
(rf/defn handle-bookmarks
[{:keys [db]} bookmarks]
(let [changed-bookmarks (reduce (fn [acc {:keys [url] :as bookmark}]
(assoc acc url bookmark))
+5 -5
View File
@@ -3,14 +3,14 @@
(:require [status-im.constants :as constants]
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[status-im.network.core :as network]
[taoensso.timbre :as log]
[status-im.utils.random :as random]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.ui.screens.browser.eip3085.sheet :as sheet]))
(fx/defn send-success-call-to-bridge
(rf/defn send-success-call-to-bridge
{:events [:eip3085/send-success-call-to-bridge]}
[_ id messageId]
{:browser/send-to-bridge {:type constants/web3-send-async-callback
@@ -19,7 +19,7 @@
:id (int id)
:result nil}}})
(fx/defn allow-permission
(rf/defn allow-permission
{:events [:eip3085.ui/dapp-permission-allowed]}
[{:keys [db] :as cofx} message-id {:keys [new-networks id]}]
{:db (assoc db :networks/networks new-networks)
@@ -29,7 +29,7 @@
:on-error #(log/error "failed to perform settings_saveSetting" %)}]
:dispatch [:bottom-sheet/hide]})
(fx/defn deny-permission
(rf/defn deny-permission
{:events [:eip3085.ui/dapp-permission-denied]}
[_ message-id _]
{:browser/send-to-bridge {:type constants/web3-send-async-callback
@@ -38,7 +38,7 @@
:message "User rejected the request."}}
:dispatch [:bottom-sheet/hide]})
(fx/defn handle-add-ethereum-chain
(rf/defn handle-add-ethereum-chain
{:events [:eip3085/handle-add-ethereum-chain]}
[{{:networks/keys [networks] :as db} :db :as cofx}
dapp-name id message-id {:keys [chainId blockExplorerUrls chainName iconUrls nativeCurrency rpcUrls] :as params}]
+3 -3
View File
@@ -1,11 +1,11 @@
;reference https://eips.ethereum.org/EIPS/eip-3326 EIP-3326: Wallet Switch Ethereum Chain RPC Method (`wallet_switchEthereumChain`)
(ns status-im.browser.eip3326
(:require [status-im.constants :as constants]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[status-im.ethereum.core :as ethereum]
[status-im.ui.screens.browser.eip3326.sheet :as sheet]))
(fx/defn deny-permission
(rf/defn deny-permission
{:events [:eip3326.ui/dapp-permission-denied]}
[{:keys [db] :as cofx} message-id _]
{:browser/send-to-bridge {:type constants/web3-send-async-callback
@@ -14,7 +14,7 @@
:message "User rejected the request."}}
:dispatch [:bottom-sheet/hide]})
(fx/defn handle-switch-ethereum-chain
(rf/defn handle-switch-ethereum-chain
{:events [:eip3326/handle-switch-ethereum-chain]}
[{:keys [db] :as cofx} dapp-name id message-id {:keys [chainId] :as params}]
(let [target-chain-id (js/parseInt chainId 16)
+19 -19
View File
@@ -4,7 +4,7 @@
[status-im.i18n.i18n :as i18n]
[status-im2.navigation.events :as navigation]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.utils.fx :as fx]))
[utils.re-frame :as rf]))
(declare process-next-permission)
(declare send-response-to-bridge)
@@ -21,18 +21,18 @@
:description (i18n/label :t/allowing-authorizes-this-dapp)
:icon :main-icons/wallet}})
(fx/defn permission-yield-control
(rf/defn permission-yield-control
[{:keys [db] :as cofx} dapp-name permission message-id params]
(cond
(= permission constants/dapp-permission-qr-code)
(fx/merge (assoc-in cofx [:db :browser/options :yielding-control?] true)
(rf/merge (assoc-in cofx [:db :browser/options :yielding-control?] true)
(qr-scanner/scan-qr-code {:handler :browser.bridge.callback/qr-code-scanned
:cancel-handler :browser.bridge.callback/qr-code-canceled
:data {:dapp-name dapp-name
:permission permission
:message-id message-id}}))))
(fx/defn permission-show-permission
(rf/defn permission-show-permission
[{:keys [db] :as cofx} dapp-name permission message-id yield-control?]
{:db (assoc-in db [:browser/options :show-permission]
{:requested-permission permission
@@ -46,7 +46,7 @@
constants/dapp-permission-web3 [(:dapps-address multiaccount)]}
allowed-permission)))
(fx/defn send-response-to-bridge
(rf/defn send-response-to-bridge
"Send response to the bridge. If the permission is allowed, send data associated
with the permission"
[{:keys [db] :as cofx} permission message-id allowed? data]
@@ -57,7 +57,7 @@
allowed?
(assoc :data data))})
(fx/defn update-dapp-permissions
(rf/defn update-dapp-permissions
[{:keys [db]} dapp-name permission allowed?]
(let [dapp-permissions-set (set (get-in db [:dapps/permissions dapp-name :permissions]))
allowed-permissions-set (if allowed?
@@ -70,23 +70,23 @@
:params [allowed-permissions]
:on-success #()}]}))
(fx/defn revoke-permissions
(rf/defn revoke-permissions
{:events [:browser/revoke-dapp-permissions]}
[{:keys [db] :as cofx} dapp]
(fx/merge cofx
(rf/merge cofx
{:db (update-in db [:dapps/permissions] dissoc dapp)
::json-rpc/call [{:method "permissions_deleteDappPermissions"
:params [dapp]
:on-success #()}]}))
(fx/defn revoke-dapp-permissions
(rf/defn revoke-dapp-permissions
{:events [:dapps/revoke-access]}
[cofx dapp]
(fx/merge cofx
(rf/merge cofx
(revoke-permissions dapp)
(navigation/navigate-back)))
(fx/defn clear-dapps-permissions
(rf/defn clear-dapps-permissions
[{:keys [db]}]
(let [dapp-permissions (keys (:dapps/permissions db))]
{:db (dissoc db :dapps/permissions)
@@ -95,7 +95,7 @@
:params [dapp]
:on-success #()})}))
(fx/defn process-next-permission
(rf/defn process-next-permission
"Process next permission by removing it from pending permissions and prompting user
if there is no pending permissions left, save all granted permissions
and return the result to the bridge"
@@ -111,40 +111,40 @@
(permission-yield-control new-cofx dapp-name permission message-id params)
(permission-show-permission new-cofx dapp-name permission message-id yield-control?)))))))
(fx/defn send-response-and-process-next-permission
(rf/defn send-response-and-process-next-permission
[{:keys [db] :as cofx} dapp-name requested-permission message-id]
(fx/merge cofx
(rf/merge cofx
(send-response-to-bridge requested-permission
message-id
true
(get-permission-data cofx requested-permission))
(process-next-permission dapp-name)))
(fx/defn allow-permission
(rf/defn allow-permission
"Add permission to set of allowed permission and process next permission"
{:events [:browser.permissions.ui/dapp-permission-allowed]}
[{:keys [db] :as cofx}]
(let [{:keys [requested-permission message-id dapp-name yield-control? params]}
(get-in db [:browser/options :show-permission])]
(fx/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(rf/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(update-dapp-permissions dapp-name requested-permission true)
(if yield-control?
(permission-yield-control dapp-name requested-permission message-id params)
(send-response-and-process-next-permission dapp-name requested-permission message-id)))))
(fx/defn deny-permission
(rf/defn deny-permission
"Add permission to set of allowed permission and process next permission"
{:events [:browser.permissions.ui/dapp-permission-denied]}
[{:keys [db] :as cofx}]
(let [{:keys [requested-permission message-id dapp-name]} (get-in db [:browser/options :show-permission])]
(fx/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(rf/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(send-response-to-bridge requested-permission
message-id
false
(get-permission-data cofx requested-permission))
(process-next-permission dapp-name))))
(fx/defn process-permission
(rf/defn process-permission
"Process the permission requested by a dapp
If supported permission is already granted, return the result immediatly to the bridge
Otherwise process the first permission which will prompt user"
+65 -55
View File
@@ -3,6 +3,8 @@
[taoensso.timbre :as log]
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im.chat.models.message-list :as message-list]
[status-im.chat.models.delete-message :as delete-message]
[status-im.chat.models.delete-message-for-me :as delete-for-me]
[status-im.data-store.chats :as chats-store]
[status-im.mailserver.core :as mailserver]
[status-im.data-store.contacts :as contacts-store]
@@ -11,7 +13,7 @@
[quo.design-system.colors :as colors]
[status-im.constants :as constants]
[status-im.utils.clocks :as utils.clocks]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[status-im.utils.utils :as utils]
[status-im.utils.types :as types]
[status-im.add-new.db :as new-public-chat.db]
@@ -105,14 +107,14 @@
(fn [val]
(and (not (get-in db [:chats (:chat-id val)])) (:public? val))))
(fx/defn leave-removed-chat
(rf/defn leave-removed-chat
[{{:keys [view-id current-chat-id chats]} :db
:as cofx}]
(when (and (= view-id :chat)
(not (contains? chats current-chat-id)))
(navigation/navigate-back cofx)))
(fx/defn ensure-chats
(rf/defn ensure-chats
"Add chats to db and update"
[{:keys [db] :as cofx} chats]
(let [{:keys [all-chats chats-home-list removed-chats]}
@@ -128,7 +130,7 @@
:chats-home-list #{}
:removed-chats #{}}
(map (map-chats cofx) chats))]
(fx/merge
(rf/merge
cofx
(merge {:db (-> db
(update :chats merge all-chats)
@@ -141,7 +143,7 @@
(get-in db [:multiaccount :remote-push-notifications-enabled?])]}))
leave-removed-chat)))
(fx/defn clear-history
(rf/defn clear-history
"Clears history of the particular chat"
[{:keys [db] :as cofx} chat-id remove-chat?]
(let [{:keys [last-message public?
@@ -163,11 +165,11 @@
:deleted-at-clock-value last-message-clock-value})
chats))))}))
(fx/defn clear-history-handler
(rf/defn clear-history-handler
"Clears history of the particular chat"
{:events [:chat.ui/clear-history]}
[{:keys [db] :as cofx} chat-id remove-chat?]
(fx/merge cofx
(rf/merge cofx
{:db db
::json-rpc/call [{:method "wakuext_clearHistory"
:params [{:id chat-id}]
@@ -175,15 +177,15 @@
:on-error #(log/error "failed to clear history " chat-id %)}]}
(clear-history chat-id remove-chat?)))
(fx/defn chat-deactivated
(rf/defn chat-deactivated
{:events [::chat-deactivated]}
[_ chat-id]
(log/debug "chat deactivated" chat-id))
(fx/defn deactivate-chat
(rf/defn deactivate-chat
"Deactivate chat in db, no side effects"
[{:keys [db now] :as cofx} chat-id]
(fx/merge
(rf/merge
cofx
{:db (-> (if (get-in db [:chats chat-id :muted])
(assoc-in db [:chats chat-id :active] false)
@@ -196,7 +198,7 @@
:on-error #(log/error "failed to create public chat" chat-id %)}]}
(clear-history chat-id true)))
(fx/defn offload-messages
(rf/defn offload-messages
{:events [:offload-messages]}
[{:keys [db]} chat-id]
(merge {:db (-> db
@@ -206,50 +208,52 @@
(when (and (= chat-id constants/timeline-chat-id) (= (:view-id db) :status))
{:dispatch [:init-timeline-chat]})))
(fx/defn close-chat
(rf/defn close-chat
{:events [:close-chat]}
[{:keys [db] :as cofx}]
(when-let [chat-id (:current-chat-id db)]
(chat.state/reset-visible-item)
(fx/merge cofx
(rf/merge cofx
{:db (dissoc db :current-chat-id)}
(delete-for-me/sync-all)
(delete-message/send-all)
(offload-messages chat-id))))
(fx/defn force-close-chat
(rf/defn force-close-chat
[{:keys [db] :as cofx} chat-id]
(do
(chat.state/reset-visible-item)
(fx/merge cofx
(rf/merge cofx
{:db (dissoc db :current-chat-id)}
(offload-messages chat-id))))
(fx/defn remove-chat
(rf/defn remove-chat
"Removes chat completely from app, producing all necessary effects for that"
{:events [:chat.ui/remove-chat]}
[{:keys [db now] :as cofx} chat-id]
(fx/merge cofx
(rf/merge cofx
{:clear-message-notifications
[[chat-id] (get-in db [:multiaccount :remote-push-notifications-enabled?])]}
(deactivate-chat chat-id)
(offload-messages chat-id)))
(fx/defn show-more-chats
(rf/defn show-more-chats
{:events [:chat.ui/show-more-chats]}
[{:keys [db]}]
(when (< (:home-items-show-number db) (count (:chats db)))
{:db (update db :home-items-show-number + 40)}))
(fx/defn preload-chat-data
(rf/defn preload-chat-data
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
{:events [:chat.ui/preload-chat-data]}
[cofx chat-id]
(loading/load-messages cofx chat-id))
(fx/defn navigate-to-chat
(rf/defn navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat.ui/navigate-to-chat]}
[{db :db :as cofx} chat-id]
(fx/merge cofx
(rf/merge cofx
{:dispatch [:navigate-to :chat]}
(navigation/change-tab :chat)
(when-not (= (:view-id db) :community)
@@ -262,23 +266,29 @@
#(when (group-chat? cofx chat-id)
(loading/load-chat % chat-id))))
(fx/defn navigate-to-chat-nav2
(rf/defn navigate-to-chat-nav2
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat.ui/navigate-to-chat-nav2]}
[{db :db :as cofx} chat-id from-switcher?]
(fx/merge cofx
{:db (assoc db :current-chat-id chat-id)}
(offload-messages chat-id)
[{db :db :as cofx} chat-id from-shell?]
(rf/merge cofx
{:dispatch [:navigate-to-nav2 :chat chat-id from-shell?]}
(when-not (= (:view-id db) :community)
(navigation/pop-to-root-tab :shell-stack))
(close-chat)
(force-close-chat chat-id)
(fn [{:keys [db]}]
{:db (assoc db :current-chat-id chat-id)})
(preload-chat-data chat-id)
(navigation/navigate-to-nav2 :chat chat-id nil from-switcher?)))
#(when (group-chat? cofx chat-id)
(loading/load-chat % chat-id))))
(fx/defn handle-clear-history-response
(rf/defn handle-clear-history-response
{:events [::history-cleared]}
[{:keys [db]} chat-id response]
(let [chat (chats-store/<-rpc (first (:chats response)))]
{:db (assoc-in db [:chats chat-id] chat)}))
(fx/defn handle-one-to-one-chat-created
(rf/defn handle-one-to-one-chat-created
{:events [::one-to-one-chat-created]}
[{:keys [db]} chat-id response]
(let [chat (chats-store/<-rpc (first (:chats response)))
@@ -293,13 +303,13 @@
(update :chats-home-list conj chat-id))
:dispatch [:chat.ui/navigate-to-chat chat-id]}))
(fx/defn navigate-to-user-pinned-messages
(rf/defn navigate-to-user-pinned-messages
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat.ui/navigate-to-pinned-messages]}
[cofx chat-id]
(navigation/navigate-to cofx :chat-pinned-messages {:chat-id chat-id}))
(fx/defn start-chat
(rf/defn start-chat
"Start a chat, making sure it exists"
{:events [:chat.ui/start-chat]}
[{:keys [db] :as cofx} chat-id ens-name]
@@ -316,7 +326,7 @@
(defn my-profile-chat-topic [db]
(profile-chat-topic (get-in db [:multiaccount :public-key])))
(fx/defn handle-public-chat-created
(rf/defn handle-public-chat-created
{:events [::public-chat-created]}
[{:keys [db]} chat-id response]
{:db (-> db
@@ -324,13 +334,13 @@
(update :chats-home-list conj chat-id))
:dispatch [:chat.ui/navigate-to-chat chat-id]})
(fx/defn create-public-chat-go [_ chat-id]
(rf/defn create-public-chat-go [_ chat-id]
{::json-rpc/call [{:method "wakuext_createPublicChat"
:params [{:id chat-id}]
:on-success #(re-frame/dispatch [::public-chat-created chat-id %])
:on-error #(log/error "failed to create public chat" chat-id %)}]})
(fx/defn start-public-chat
(rf/defn start-public-chat
"Starts a new public chat"
{:events [:chat.ui/start-public-chat]}
[cofx topic]
@@ -343,10 +353,10 @@
{:utils/show-popup {:title (i18n/label :t/cant-open-public-chat)
:content (i18n/label :t/invalid-public-chat-topic)}}))
(fx/defn profile-chat-created
(rf/defn profile-chat-created
{:events [::profile-chat-created]}
[{:keys [db] :as cofx} chat-id response navigate-to?]
(fx/merge
(rf/merge
cofx
{:db db}
#(when response
@@ -356,7 +366,7 @@
{:dispatch-n [[:chat.ui/preload-chat-data chat-id]
[:open-modal :profile]]})))
(fx/defn start-profile-chat
(rf/defn start-profile-chat
"Starts a new profile chat"
{:events [:start-profile-chat]}
[cofx profile-public-key navigate-to?]
@@ -368,7 +378,7 @@
:on-success #(re-frame/dispatch [::profile-chat-created chat-id % navigate-to?])
:on-error #(log/error "failed to create profile chat" chat-id %)}]})))
(fx/defn disable-chat-cooldown
(rf/defn disable-chat-cooldown
"Turns off chat cooldown (protection against message spamming)"
{:events [:chat/disable-cooldown]}
[{:keys [db]}]
@@ -382,18 +392,18 @@
(i18n/label :cooldown/warning-message)
#())))
(fx/defn mute-chat-failed
(rf/defn mute-chat-failed
{:events [::mute-chat-failed]}
[{:keys [db] :as cofx} chat-id muted? error]
(log/error "mute chat failed" chat-id error)
{:db (assoc-in db [:chats chat-id :muted] (not muted?))})
(fx/defn mute-chat-toggled-successfully
(rf/defn mute-chat-toggled-successfully
{:events [::mute-chat-toggled-successfully]}
[_ chat-id]
(log/debug "muted chat successfully" chat-id))
(fx/defn mute-chat
(rf/defn mute-chat
{:events [::mute-chat-toggled]}
[{:keys [db] :as cofx} chat-id muted?]
(let [method (if muted? "wakuext_muteChat" "wakuext_unmuteChat")]
@@ -403,19 +413,19 @@
:on-error #(re-frame/dispatch [::mute-chat-failed chat-id muted? %])
:on-success #(re-frame/dispatch [::mute-chat-toggled-successfully chat-id])}]}))
(fx/defn show-profile
(rf/defn show-profile
{:events [:chat.ui/show-profile]}
[{:keys [db] :as cofx} identity ens-name]
(let [my-public-key (get-in db [:multiaccount :public-key])]
(when (not= my-public-key identity)
(fx/merge
(rf/merge
cofx
{:db (-> db
(assoc :contacts/identity identity)
(assoc :contacts/ens-name ens-name))}
(start-profile-chat identity true)))))
(fx/defn clear-history-pressed
(rf/defn clear-history-pressed
{:events [:chat.ui/clear-history-pressed]}
[_ chat-id]
{:ui/show-confirmation
@@ -426,19 +436,19 @@
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch [:chat.ui/clear-history chat-id false]))}})
(fx/defn gaps-failed
(rf/defn gaps-failed
{:events [::gaps-failed]}
[{:keys [db]} chat-id gap-ids error]
(log/error "failed to fetch gaps" chat-id gap-ids error)
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
(fx/defn sync-chat-from-sync-from-failed
(rf/defn sync-chat-from-sync-from-failed
{:events [::sync-chat-from-sync-from-failed]}
[{:keys [db]} chat-id error]
(log/error "failed to sync chat" chat-id error)
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
(fx/defn sync-chat-from-sync-from-success
(rf/defn sync-chat-from-sync-from-success
{:events [::sync-chat-from-sync-from-success]}
[{:keys [db] :as cofx} chat-id synced-from]
(log/debug "synced success" chat-id synced-from)
@@ -447,24 +457,24 @@
(assoc-in [:chats chat-id :synced-from] synced-from)
(dissoc :mailserver/fetching-gaps-in-progress))})
(fx/defn gaps-filled
(rf/defn gaps-filled
{:events [::gaps-filled]}
[{:keys [db] :as cofx} chat-id message-ids]
(fx/merge
(rf/merge
cofx
{:db (-> db
(update-in [:messages chat-id] (fn [messages] (apply dissoc messages message-ids)))
(dissoc :mailserver/fetching-gaps-in-progress))}
(message-list/rebuild-message-list chat-id)))
(fx/defn fill-gaps
(rf/defn fill-gaps
[cofx chat-id gap-ids]
{::json-rpc/call [{:method "wakuext_fillGaps"
:params [chat-id gap-ids]
:on-success #(re-frame/dispatch [::gaps-filled chat-id gap-ids %])
:on-error #(re-frame/dispatch [::gaps-failed chat-id gap-ids %])}]})
(fx/defn sync-chat-from-sync-from
(rf/defn sync-chat-from-sync-from
[cofx chat-id]
(log/debug "syncing chat from sync from")
{::json-rpc/call [{:method "wakuext_syncChatFromSyncedFrom"
@@ -472,19 +482,19 @@
:on-success #(re-frame/dispatch [::sync-chat-from-sync-from-success chat-id %])
:on-error #(re-frame/dispatch [::sync-chat-from-sync-from-failed chat-id %])}]})
(fx/defn chat-ui-fill-gaps
(rf/defn chat-ui-fill-gaps
{:events [:chat.ui/fill-gaps]}
[{:keys [db] :as cofx} chat-id gap-ids]
(let [use-status-nodes? (mailserver/fetch-use-mailservers? {:db db})]
(log/info "filling gaps if use-status-nodes = true" chat-id gap-ids)
(when use-status-nodes?
(fx/merge cofx
(rf/merge cofx
{:db (assoc db :mailserver/fetching-gaps-in-progress gap-ids)}
(if (= gap-ids #{:first-gap})
(sync-chat-from-sync-from chat-id)
(fill-gaps chat-id gap-ids))))))
(fx/defn chat-ui-remove-chat-pressed
(rf/defn chat-ui-remove-chat-pressed
{:events [:chat.ui/remove-chat-pressed]}
[_ chat-id]
{:ui/show-confirmation
@@ -495,7 +505,7 @@
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch [:chat.ui/remove-chat chat-id]))}})
(fx/defn decrease-unviewed-count
(rf/defn decrease-unviewed-count
{:events [:chat/decrease-unviewed-count]}
[{:keys [db]} chat-id {:keys [count countWithMentions]}]
{:db (-> db
@@ -0,0 +1,108 @@
(ns status-im.chat.models.delete-message
(:require [re-frame.core :as re-frame]
[status-im.chat.models.message-list :as message-list]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.datetime :as datetime]
[utils.re-frame :as rf]
[taoensso.encore :as enc]
[taoensso.timbre :as log]))
(defn- update-db-clear-undo-timer
[db chat-id message-id]
(when (get-in db [:messages chat-id message-id])
(update-in db
[:messages chat-id message-id]
dissoc
:deleted-undoable-till)))
(defn- update-db-delete-locally
"Delete message in re-frame db and set the undo timelimit"
[db chat-id message-id undo-time-limit-ms]
(when (get-in db [:messages chat-id message-id])
(update-in db
[:messages chat-id message-id]
assoc
:deleted? true
:deleted-undoable-till (+ (datetime/timestamp)
undo-time-limit-ms))))
(defn- update-db-undo-locally
"Restore deleted message if called within timelimit"
[db chat-id message-id]
(let [{:keys [deleted? deleted-undoable-till]}
(get-in db [:messages chat-id message-id])]
(if (and deleted?
(> deleted-undoable-till (datetime/timestamp)))
(update-in db
[:messages chat-id message-id]
dissoc
:deleted?
:deleted-undoable-till)
(update-db-clear-undo-timer db chat-id message-id))))
(defn- update-db-delete-locally-without-time-limit
"Delete message in re-frame db, used to handle received removed-messages"
[db chat-id message-id]
(when (get-in db [:messages chat-id message-id])
(update-in db [:messages chat-id message-id] assoc :deleted? true)))
(rf/defn delete
"Delete message now locally and broadcast after undo time limit timeout"
{:events [:chat.ui/delete-message]}
[{:keys [db]} {:keys [chat-id message-id]} undo-time-limit-ms]
(when (get-in db [:messages chat-id message-id])
(assoc
(message-list/rebuild-message-list
{:db (update-db-delete-locally db chat-id message-id undo-time-limit-ms)}
chat-id)
:utils/dispatch-later [{:dispatch [:chat.ui/delete-message-and-send
{:chat-id chat-id
:message-id message-id}]
:ms undo-time-limit-ms}])))
(rf/defn undo
{:events [:chat.ui/undo-delete-message]}
[{:keys [db]} {:keys [chat-id message-id]}]
(when (get-in db [:messages chat-id message-id])
(message-list/rebuild-message-list
{:db (update-db-undo-locally db chat-id message-id)}
chat-id)))
(rf/defn delete-and-send
{:events [:chat.ui/delete-message-and-send]}
[{:keys [db]} {:keys [message-id chat-id]}]
(when-let [message (get-in db [:messages chat-id message-id])]
(enc/assoc-when
{:db (update-db-clear-undo-timer db chat-id message-id)
::json-rpc/call [{:method "wakuext_deleteMessageAndSend"
:params [message-id]
:js-response true
:on-error #(log/error "failed to delete message " {:message-id message-id :error %})
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]}
:dispatch (and (get-in db [:pin-messages chat-id message-id])
[:pin-message/send-pin-message {:chat-id chat-id :message-id message-id :pinned false}]))))
(defn- filter-pending-send-messages
"traverse all messages find not yet synced deleted? messages"
[acc chat-id messages]
(->> messages
(filter (fn [[_ {:keys [deleted? deleted-undoable-till]}]] (and deleted? deleted-undoable-till)))
(map (fn [message] {:chat-id chat-id :message-id (first message)}))
(concat acc)))
(rf/defn send-all
"Get all deleted messages that not yet synced with status-go and send them"
{:events [:chat.ui/send-all-deleted-messages]}
[{:keys [db] :as cofx}]
(let [pending-send-messages (reduce-kv filter-pending-send-messages [] (:messages db))]
(apply rf/merge cofx (map delete-and-send pending-send-messages))))
(rf/defn delete-messages-localy
"Mark messages :deleted? localy in client"
{:events [:chat.ui/delete-messages-localy]}
[{:keys [db]} messages chat-id]
(let [new-db (->> messages
(filter #(get-in db [:messages chat-id (:message-id %)]))
(reduce #(update-db-delete-locally-without-time-limit %1 chat-id (:message-id %2)) db))]
(when new-db
(message-list/rebuild-message-list {:db new-db} chat-id))))
@@ -3,7 +3,7 @@
[status-im.chat.models.message-list :as message-list]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.datetime :as datetime]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[taoensso.timbre :as log]))
(defn- update-db-clear-undo-timer
@@ -39,7 +39,7 @@
:deleted-for-me-undoable-till)
(update-db-clear-undo-timer db chat-id message-id))))
(fx/defn delete
(rf/defn delete
"Delete message for me now locally and broadcast after undo time limit timeout"
{:events [:chat.ui/delete-message-for-me]}
[{:keys [db]} {:keys [chat-id message-id]} undo-time-limit-ms]
@@ -53,7 +53,7 @@
:message-id message-id}]
:ms undo-time-limit-ms}])))
(fx/defn undo
(rf/defn undo
{:events [:chat.ui/undo-delete-message-for-me]}
[{:keys [db]} {:keys [chat-id message-id]}]
(when (get-in db [:messages chat-id message-id])
@@ -61,7 +61,7 @@
{:db (update-db-undo-locally db chat-id message-id)}
chat-id)))
(fx/defn delete-and-sync
(rf/defn delete-and-sync
{:events [:chat.ui/delete-message-for-me-and-sync]}
[{:keys [db]} {:keys [message-id chat-id]}]
(when (get-in db [:messages chat-id message-id])
@@ -69,22 +69,20 @@
::json-rpc/call [{:method "wakuext_deleteMessageForMeAndSync"
:params [chat-id message-id]
:js-response true
:on-error #(log/error "failed to delete message for me " %)
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response
%])}]}))
(defn- chats-reducer
"traverse all messages find not yet synced deleted-for-me? messages, generate dispatch vector"
[acc chat-id messages]
(reduce-kv
(fn [inner-acc message-id {:keys [deleted-for-me? deleted-for-me-undoable-till]}]
(if (and deleted-for-me? deleted-for-me-undoable-till)
(conj inner-acc [:chat.ui/delete-message-for-me-and-sync chat-id message-id])
inner-acc))
acc
messages))
:on-error #(log/error "failed to delete message for me, message id: " {:message-id message-id :error %})
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]}))
(fx/defn sync-all
(defn- filter-pending-sync-messages
"traverse all messages find not yet synced deleted-for-me? messages"
[acc chat-id messages]
(->> messages
(filter (fn [[_ {:keys [deleted-for-me? deleted-for-me-undoable-till]}]] (and deleted-for-me? deleted-for-me-undoable-till)))
(map (fn [message] {:chat-id chat-id :message-id (first message)}))
(concat acc)))
(rf/defn sync-all
"Get all deleted-for-me messages that not yet synced with status-go and sync them"
{:events [:chat.ui/sync-all-deleted-for-me-messages]}
[{:keys [db]}]
{:dispatch-n (reduce-kv chats-reducer [] (:messages db))})
[{:keys [db] :as cofx}]
(let [pending-sync-messages (reduce-kv filter-pending-sync-messages [] (:messages db))]
(apply rf/merge cofx (map delete-and-sync pending-sync-messages))))
@@ -4,94 +4,97 @@
delete-message-for-me]
[status-im.utils.datetime :as datetime]))
(defonce mid "message-id")
(defonce cid "chat-id")
(def mid "message-id")
(def cid "chat-id")
(deftest delete-for-me
(with-redefs [datetime/timestamp (constantly 1)]
(let [db {:messages {cid {mid {:id mid :whisper-timestamp 1}}}}
message {:message-id mid :chat-id cid}]
(testing "delete for me"
(let [result-message (get-in (delete-message-for-me/delete {:db db} message 1000)
(testing "dispatch right db fx"
(let [result-message (get-in (delete-message-for-me/delete {:db db} message 1000)
[:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (true? (:deleted-for-me? result-message)))
(is (= (:deleted-for-me-undoable-till result-message) 1001))))
(testing "should return nil if message not in db"
(is (= (delete-message-for-me/delete {:db {:messages []}} message 1000)
nil))))))
(is (= (:id result-message) mid))
(is (true? (:deleted-for-me? result-message)))
(is (= (:deleted-for-me-undoable-till result-message) 1001))))
(testing "return nil if message not in db"
(is (= (delete-message-for-me/delete {:db {:messages []}} message 1000)
nil)))))))
(deftest undo-delete-for-me
(let [db {:messages {cid {mid {:id mid :whisper-timestamp 1}}}}
message {:message-id mid :chat-id cid}]
(testing "undo delete for me in time"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till
(+ (datetime/timestamp) 1000))
result-message (get-in (delete-message-for-me/undo {:db db} message)
[:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted-for-me? result-message)))
(is (nil? (:deleted-for-me-undoable-till result-message)))))
(testing "undo delete for me"
(testing "in time"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till
(+ (datetime/timestamp) 1000))
result-message (get-in (delete-message-for-me/undo {:db db} message)
[:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted-for-me? result-message)))
(is (nil? (:deleted-for-me-undoable-till result-message)))))
(testing "remain deleted for me when undo delete for me late"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till (- (datetime/timestamp) 1000))
result-message (get-in (delete-message-for-me/undo {:db db} message) [:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted-for-me-undoable-till result-message)))
(is (true? (:deleted-for-me? result-message)))))
(testing "remain deleted for me when undo after timelimit"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted-for-me? true
:deleted-for-me-undoable-till (- (datetime/timestamp) 1000))
result-message (get-in (delete-message-for-me/undo {:db db} message) [:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted-for-me-undoable-till result-message)))
(is (true? (:deleted-for-me? result-message)))))
(testing "should return nil if message not in db"
(is (= (delete-message-for-me/undo {:db {:messages []}} message)
nil)))))
(testing "return nil if message not in db"
(is (= (delete-message-for-me/undo {:db {:messages []}} message)
nil))))))
(deftest delete-for-me-and-sync
(let [db {:messages {cid {mid {:id mid}}}}
message {:message-id mid :chat-id cid}]
(testing "delete for me and sync"
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message-for-me/delete-and-sync {:db db} message)
result-db (:db effects)
rpc-calls (:status-im.ethereum.json-rpc/call effects)]
(is (= result-db expected-db))
(is (= (count rpc-calls) 1))
(is (= (-> rpc-calls
first
:method)
"wakuext_deleteMessageForMeAndSync"))
(is (= (-> rpc-calls
first
:params
count)
2))
(is (= (-> rpc-calls
first
:params
first)
cid))
(is (= (-> rpc-calls
first
:params
second)
mid))))
(testing "delete for me and sync, should clean undo timer"
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message-for-me/delete-and-sync
{:db (update-in db
[:messages cid mid
:deleted-for-me-undoable-till]
(constantly (datetime/timestamp)))}
message)
result-db (:db effects)]
(is (= result-db expected-db))))
(testing "should return nil if message not in db"
(is (= (delete-message-for-me/delete-and-sync {:db {:messages []}}
message)
nil)))))
(testing "dispatch right rpc call"
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message-for-me/delete-and-sync {:db db} message)
result-db (:db effects)
rpc-calls (:status-im.ethereum.json-rpc/call effects)]
(is (= result-db expected-db))
(is (= (count rpc-calls) 1))
(is (= (-> rpc-calls
first
:method)
"wakuext_deleteMessageForMeAndSync"))
(is (= (-> rpc-calls
first
:params
count)
2))
(is (= (-> rpc-calls
first
:params
first)
cid))
(is (= (-> rpc-calls
first
:params
second)
mid))))
(testing "clean undo timer"
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message-for-me/delete-and-sync
{:db (update-in db
[:messages cid mid
:deleted-for-me-undoable-till]
(constantly (datetime/timestamp)))}
message)
result-db (:db effects)]
(is (= result-db expected-db))))
(testing "return nil if message not in db"
(is (= (delete-message-for-me/delete-and-sync {:db {:messages []}}
message)
nil))))))
@@ -0,0 +1,95 @@
(ns status-im.chat.models.delete-message-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[status-im.chat.models.delete-message :as delete-message]
[status-im.utils.datetime :as datetime]))
(def mid "message-id")
(def cid "chat-id")
(deftest delete
(with-redefs [datetime/timestamp (constantly 1)]
(let [db {:messages {cid {mid {:id mid :whisper-timestamp 1}}}}
message {:message-id mid :chat-id cid}]
(testing "delete"
(testing "dispatch right db fx"
(let [result-message (get-in (delete-message/delete {:db db} message 1000)
[:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (true? (:deleted? result-message)))
(is (= (:deleted-undoable-till result-message) 1001))))
(testing "return nil if message not in db"
(is (= (delete-message/delete {:db {:messages []}} message 1000)
nil)))))))
(deftest undo-delete
(let [db {:messages {cid {mid {:id mid :whisper-timestamp 1}}}}
message {:message-id mid :chat-id cid}]
(testing "undo delete"
(testing "undo in time"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted? true
:deleted-undoable-till
(+ (datetime/timestamp) 1000))
result-message (get-in (delete-message/undo {:db db} message)
[:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted? result-message)))
(is (nil? (:deleted-undoable-till result-message)))))
(testing "remain deleted when undo after timelimit"
(let [db (update-in db
[:messages cid mid]
assoc
:deleted? true
:deleted-undoable-till (- (datetime/timestamp) 1000))
result-message (get-in (delete-message/undo {:db db} message) [:db :messages cid mid])]
(is (= (:id result-message) mid))
(is (nil? (:deleted-undoable-till result-message)))
(is (true? (:deleted? result-message)))))
(testing "return nil if message not in db"
(is (= (delete-message/undo {:db {:messages []}} message)
nil))))))
(deftest delete-and-send
(let [db {:messages {cid {mid {:id mid}}}}
message {:message-id mid :chat-id cid}]
(testing "delete and send"
(testing "dispatch right rpc call fx"
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message/delete-and-send {:db db} message)
result-db (:db effects)
rpc-calls (:status-im.ethereum.json-rpc/call effects)]
(is (= result-db expected-db))
(is (= (count rpc-calls) 1))
(is (= (-> rpc-calls
first
:method)
"wakuext_deleteMessageAndSend"))
(is (= (-> rpc-calls
first
:params
count)
1))
(is (= (-> rpc-calls
first
:params
first)
mid))))
(testing "clean undo timer"
(let [expected-db {:messages {cid {mid {:id mid}}}}
effects (delete-message/delete-and-send
{:db (update-in db
[:messages cid mid
:deleted-undoable-till]
(constantly (datetime/timestamp)))}
message)
result-db (:db effects)]
(is (= result-db expected-db))))
(testing "return nil if message not in db"
(is (= (delete-message/delete-and-send {:db {:messages []}}
message)
nil))))))
+16 -16
View File
@@ -1,6 +1,6 @@
(ns status-im.chat.models.images
(:require [re-frame.core :as re-frame]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
["@react-native-community/cameraroll" :as CameraRoll]
["react-native-blob-util" :default ReactNativeBlobUtil]
[status-im.utils.types :as types]
@@ -108,7 +108,7 @@
(.then #(re-frame/dispatch [:on-camera-roll-get-photos (:edges (types/js->clj %))]))
(.catch #(log/warn "could not get cameraroll photos"))))})))
(fx/defn image-captured
(rf/defn image-captured
{:events [:chat.ui/image-captured]}
[{:keys [db]} chat-id uri]
(let [current-chat-id (or chat-id (:current-chat-id db))
@@ -117,44 +117,44 @@
(not (get images uri)))
{::image-selected [uri current-chat-id]})))
(fx/defn camera-roll-get-photos
(rf/defn camera-roll-get-photos
{:events [:chat.ui/camera-roll-get-photos]}
[_ num]
{::camera-roll-get-photos num})
(fx/defn on-camera-roll-get-photos
(rf/defn on-camera-roll-get-photos
{:events [:on-camera-roll-get-photos]}
[{db :db} photos]
{:db (assoc db :camera-roll-photos (mapv #(get-in % [:node :image :uri]) photos))})
(fx/defn clear-sending-images
(rf/defn clear-sending-images
{:events [:chat.ui/clear-sending-images]}
[{:keys [db]} current-chat-id]
{:db (update-in db [:chat/inputs current-chat-id :metadata] assoc :sending-image {})})
(fx/defn cancel-sending-image
(rf/defn cancel-sending-image
{:events [:chat.ui/cancel-sending-image]}
[{:keys [db] :as cofx} chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))]
(clear-sending-images cofx current-chat-id)))
(fx/defn cancel-sending-image-timeline
(rf/defn cancel-sending-image-timeline
{:events [:chat.ui/cancel-sending-image-timeline]}
[{:keys [db] :as cofx}]
(cancel-sending-image cofx (chat/my-profile-chat-topic db)))
(fx/defn image-selected
(rf/defn image-selected
{:events [:chat.ui/image-selected]}
[{:keys [db]} current-chat-id original uri]
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image original] merge {:uri uri})})
(fx/defn image-unselected
(rf/defn image-unselected
{:events [:chat.ui/image-unselected]}
[{:keys [db]} original]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image] dissoc original)}))
(fx/defn chat-open-image-picker
(rf/defn chat-open-image-picker
{:events [:chat.ui/open-image-picker]}
[{:keys [db]} chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))
@@ -162,12 +162,12 @@
(when (< (count images) config/max-images-batch)
{::chat-open-image-picker current-chat-id})))
(fx/defn chat-open-image-picker-timeline
(rf/defn chat-open-image-picker-timeline
{:events [:chat.ui/open-image-picker-timeline]}
[{:keys [db] :as cofx}]
(chat-open-image-picker cofx (chat/my-profile-chat-topic db)))
(fx/defn chat-show-image-picker-camera
(rf/defn chat-show-image-picker-camera
{:events [:chat.ui/show-image-picker-camera]}
[{:keys [db]} chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))
@@ -175,12 +175,12 @@
(when (< (count images) config/max-images-batch)
{::chat-open-image-picker-camera current-chat-id})))
(fx/defn chat-show-image-picker-camera-timeline
(rf/defn chat-show-image-picker-camera-timeline
{:events [:chat.ui/show-image-picker-camera-timeline]}
[{:keys [db] :as cofx}]
(chat-show-image-picker-camera cofx (chat/my-profile-chat-topic db)))
(fx/defn camera-roll-pick
(rf/defn camera-roll-pick
{:events [:chat.ui/camera-roll-pick]}
[{:keys [db]} uri chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))
@@ -192,12 +192,12 @@
(not (get images uri)))
{::image-selected [uri current-chat-id]}))))
(fx/defn camera-roll-pick-timeline
(rf/defn camera-roll-pick-timeline
{:events [:chat.ui/camera-roll-pick-timeline]}
[{:keys [db] :as cofx} uri]
(camera-roll-pick cofx uri (chat/my-profile-chat-topic db)))
(fx/defn save-image-to-gallery
(rf/defn save-image-to-gallery
{:events [:chat.ui/save-image-to-gallery]}
[_ base64-uri]
{::save-image-to-gallery base64-uri})
+43 -52
View File
@@ -1,20 +1,20 @@
(ns status-im.chat.models.input
(:require [clojure.string :as string]
(:require ["emojilib" :as emojis]
[clojure.string :as string]
[goog.object :as object]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.chat.constants :as chat.constants]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.chat.models :as chat]
[status-im.chat.models.mentions :as mentions]
[status-im.chat.models.message :as chat.message]
[status-im.chat.models.message-content :as message-content]
[status-im.constants :as constants]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.i18n.i18n :as i18n]
[status-im.utils.datetime :as datetime]
[status-im.utils.fx :as fx]
["emojilib" :as emojis]
[status-im.chat.models.mentions :as mentions]
[status-im.utils.utils :as utils]))
[utils.re-frame :as rf]
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]))
(defn text->emoji
"Replaces emojis in a specified `text`"
@@ -26,7 +26,7 @@
(.-char ^js emoji-map)
original))))
(fx/defn set-chat-input-text
(rf/defn set-chat-input-text
"Set input text for current-chat. Takes db and input text and cofx
as arguments and returns new fx. Always clear all validation messages."
{:events [:chat.ui/set-chat-input-text]}
@@ -34,24 +34,25 @@
(let [current-chat-id (or chat-id (:current-chat-id db))]
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))}))
(fx/defn set-timeline-input-text
(rf/defn set-timeline-input-text
{:events [:chat.ui/set-timeline-input-text]}
[{db :db} new-input]
{:db (assoc-in db [:chat/inputs (chat/my-profile-chat-topic db) :input-text]
(text->emoji new-input))})
(fx/defn select-mention
(rf/defn select-mention
{:events [:chat.ui/select-mention]}
[{:keys [db] :as cofx} text-input-ref {:keys [alias name searched-text match] :as user}]
(let [chat-id (:current-chat-id db)
new-text (mentions/new-input-text-with-mention cofx user)
at-sign-idx (get-in db [:chats/mentions chat-id :mentions :at-sign-idx])
cursor (+ at-sign-idx (count name) 2)]
(fx/merge
(rf/merge
cofx
{:db (-> db
(assoc-in [:chats/cursor chat-id] cursor)
(assoc-in [:chats/mention-suggestions chat-id] nil))}
{:db (-> db
(assoc-in [:chats/cursor chat-id] cursor)
(assoc-in [:chats/mention-suggestions chat-id] nil))
:set-text-input-value [chat-id new-text text-input-ref]}
(set-chat-input-text new-text chat-id)
;; NOTE(rasom): Some keyboards do not react on selection property passed to
;; text input (specifically Samsung keyboard with predictive text set on).
@@ -86,7 +87,7 @@
:chat/spam-messages-frequency 0
:chat/cooldown-enabled? true)})
(fx/defn process-cooldown
(rf/defn process-cooldown
"Process cooldown to protect against message spammers"
[{{:keys [chat/last-outgoing-message-sent-at
chat/cooldowns
@@ -105,12 +106,12 @@
(and spamming-fast? spamming-frequently?)
(start-cooldown (inc cooldowns))))))
(fx/defn reply-to-message
(rf/defn reply-to-message
"Sets reference to previous chat message and focuses on input"
{:events [:chat.ui/reply-to-message]}
[{:keys [db] :as cofx} message]
(let [current-chat-id (:current-chat-id db)]
(fx/merge cofx
(rf/merge cofx
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message]
message)
@@ -118,7 +119,7 @@
(update-in [:chat/inputs current-chat-id :metadata]
dissoc :sending-image))})))
(fx/defn edit-message
(rf/defn edit-message
"Sets reference to previous chat message and focuses on input"
{:events [:chat.ui/edit-message]}
[{:keys [db] :as cofx} message]
@@ -133,17 +134,7 @@
(update-in [:chat/inputs current-chat-id :metadata]
dissoc :sending-image))}))
(fx/defn soft-delete-message
"Does't delete from db, this is a soft delete"
{:events [:chat.ui/soft-delete-message]}
[{:keys [db] :as cofx} {:keys [message-id chat-id]}]
{::json-rpc/call [{:method "wakuext_deleteMessageAndSend"
:params [message-id]
:js-response true
:on-error #(log/error "failed to delete message message " %)
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]})
(fx/defn show-contact-request-input
(rf/defn show-contact-request-input
"Sets reference to previous chat message and focuses on input"
{:events [:chat.ui/send-contact-request]}
[{:keys [db] :as cofx}]
@@ -157,7 +148,7 @@
(update-in [:chat/inputs current-chat-id :metadata]
dissoc :sending-image))}))
(fx/defn cancel-message-reply
(rf/defn cancel-message-reply
"Cancels stage message reply"
{:events [:chat.ui/cancel-message-reply]}
[{:keys [db]}]
@@ -190,8 +181,8 @@
:text (i18n/label :t/update-to-see-image {"locale" "en"})})
images)))
(fx/defn clean-input [{:keys [db] :as cofx} current-chat-id]
(fx/merge cofx
(rf/defn clean-input [{:keys [db] :as cofx} current-chat-id]
(rf/merge cofx
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :sending-contact-request] nil)
(assoc-in [:chat/inputs current-chat-id :metadata :sending-image] nil)
@@ -199,28 +190,28 @@
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil))}
(set-chat-input-text nil current-chat-id)))
(fx/defn cancel-message-edit
(rf/defn cancel-message-edit
"Cancels stage message edit"
{:events [:chat.ui/cancel-message-edit]}
[{:keys [db] :as cofx}]
(let [current-chat-id (:current-chat-id db)]
(fx/merge cofx
{:set-input-text [current-chat-id ""]}
(rf/merge cofx
{:set-text-input-value [current-chat-id ""]}
(clean-input current-chat-id)
(mentions/clear-mentions)
(mentions/clear-cursor))))
(fx/defn send-messages [{:keys [db] :as cofx} input-text current-chat-id]
(rf/defn send-messages [{:keys [db] :as cofx} input-text current-chat-id]
(let [image-messages (build-image-messages cofx current-chat-id)
text-message (build-text-message cofx input-text current-chat-id)
messages (keep identity (conj image-messages text-message))]
(when (seq messages)
(fx/merge cofx
(rf/merge cofx
(clean-input (:current-chat-id db))
(process-cooldown)
(chat.message/send-messages messages)))))
(fx/defn send-my-status-message
(rf/defn send-my-status-message
"when not empty, proceed by sending text message with public key topic"
{:events [:profile.ui/send-my-status-message]}
[{db :db :as cofx}]
@@ -230,11 +221,11 @@
text-message (build-text-message cofx input-text current-chat-id)
messages (keep identity (conj image-messages text-message))]
(when (seq messages)
(fx/merge cofx
(rf/merge cofx
(clean-input current-chat-id)
(chat.message/send-messages messages)))))
(fx/defn send-audio-message
(rf/defn send-audio-message
[cofx audio-path duration current-chat-id]
(when-not (string/blank? audio-path)
(chat.message/send-message cofx {:chat-id current-chat-id
@@ -243,7 +234,7 @@
:audio-duration-ms duration
:text (i18n/label :t/update-to-listen-audio {"locale" "en"})})))
(fx/defn send-sticker-message
(rf/defn send-sticker-message
[cofx {:keys [hash packID pack]} current-chat-id]
(when-not (or (string/blank? hash) (and (string/blank? packID) (string/blank? pack)))
(chat.message/send-message cofx {:chat-id current-chat-id
@@ -252,8 +243,8 @@
:pack (int (if (string/blank? packID) pack packID))}
:text (i18n/label :t/update-to-see-sticker {"locale" "en"})})))
(fx/defn send-edited-message [{:keys [db] :as cofx} text {:keys [message-id quoted-message]}]
(fx/merge
(rf/defn send-edited-message [{:keys [db] :as cofx} text {:keys [message-id quoted-message]}]
(rf/merge
cofx
{::json-rpc/call [{:method "wakuext_editMessage"
:params [{:id message-id
@@ -267,24 +258,24 @@
(cancel-message-edit)
(process-cooldown)))
(fx/defn send-current-message
(rf/defn send-current-message
"Sends message from current chat input"
{:events [:chat.ui/send-current-message]}
[{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [{:keys [input-text metadata]} (get-in db [:chat/inputs current-chat-id])
editing-message (:editing-message metadata)
input-text-with-mentions (mentions/check-mentions cofx input-text)]
(fx/merge cofx
(rf/merge cofx
(if editing-message
(send-edited-message input-text-with-mentions editing-message)
(send-messages input-text-with-mentions current-chat-id))
(mentions/clear-mentions)
(mentions/clear-cursor))))
(fx/defn send-contact-request
(rf/defn send-contact-request
{:events [:contacts/send-contact-request]}
[{:keys [db] :as cofx} public-key message]
(fx/merge cofx
(rf/merge cofx
{:chat.ui/clear-inputs nil
:chat.ui/clear-inputs-old nil
::json-rpc/call [{:method "wakuext_sendContactRequest"
@@ -297,22 +288,22 @@
(clean-input (:current-chat-id db))
(process-cooldown)))
(fx/defn cancel-contact-request
(rf/defn cancel-contact-request
"Cancels contact request"
{:events [:chat.ui/cancel-contact-request]}
[{:keys [db] :as cofx}]
(let [current-chat-id (:current-chat-id db)]
(fx/merge cofx
(rf/merge cofx
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-contact-request] nil)}
(mentions/clear-mentions)
(mentions/clear-cursor)
(clean-input (:current-chat-id db))
(process-cooldown))))
(fx/defn chat-send-sticker
(rf/defn chat-send-sticker
{:events [:chat/send-sticker]}
[{{:keys [current-chat-id] :as db} :db :as cofx} {:keys [hash packID pack] :as sticker}]
(fx/merge
(rf/merge
cofx
{:db (update db
:stickers/recent-stickers
@@ -323,7 +314,7 @@
:on-success #()}]}
(send-sticker-message sticker current-chat-id)))
(fx/defn chat-send-audio
(rf/defn chat-send-audio
{:events [:chat/send-audio]}
[{{:keys [current-chat-id]} :db :as cofx} audio-path duration]
(send-audio-message cofx audio-path duration current-chat-id))
+14 -14
View File
@@ -1,15 +1,15 @@
(ns status-im.chat.models.link-preview
(:require [re-frame.core :as re-frame]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.communities.core :as models.communities]
[taoensso.timbre :as log]))
(fx/defn enable
(rf/defn enable
{:events [::enable]}
[{{:keys [multiaccount]} :db :as cofx} site enabled?]
(fx/merge cofx
(rf/merge cofx
(multiaccounts.update/multiaccount-update
:link-previews-enabled-sites
(if enabled?
@@ -17,10 +17,10 @@
(disj (get multiaccount :link-previews-enabled-sites #{}) site))
{})))
(fx/defn enable-all
(rf/defn enable-all
{:events [::enable-all]}
[{{:keys [multiaccount]} :db :as cofx} link-previews-whitelist enabled?]
(fx/merge cofx
(rf/merge cofx
(multiaccounts.update/multiaccount-update
:link-previews-enabled-sites
(if enabled?
@@ -37,7 +37,7 @@
(defn community-resolving [db community-id]
(assoc-in db [:communities/resolve-community-info community-id] true))
(fx/defn handle-community-failed-to-resolve
(rf/defn handle-community-failed-to-resolve
{:events [::community-failed-to-resolve]}
[{:keys [db]} community-id]
{:db (community-failed-to-resolve db community-id)})
@@ -45,17 +45,17 @@
(defn community-link [id]
(str "https://join.status.im/c/" id))
(fx/defn handle-community-resolved
(rf/defn handle-community-resolved
{:events [::community-resolved]}
[{:keys [db] :as cofx} community-id community]
(fx/merge cofx
(rf/merge cofx
(cond-> {:db (community-resolved db community-id)}
(some? community)
(assoc :dispatch [::cache-link-preview-data
(community-link community-id) community]))
(models.communities/handle-community community)))
(fx/defn resolve-community-info
(rf/defn resolve-community-info
{:events [::resolve-community-info]}
[{:keys [db]} community-id]
{:db (community-resolving db community-id)
@@ -66,7 +66,7 @@
(re-frame/dispatch [::community-failed-to-resolve community-id])
(log/error "Failed to request community info from mailserver"))}]})
(fx/defn load-link-preview-data
(rf/defn load-link-preview-data
{:events [::load-link-preview-data]}
[cofx link]
{::json-rpc/call [{:method "wakuext_getLinkPreviewData"
@@ -76,7 +76,7 @@
link
{:error (str "Can't get preview data for " link)}])}]})
(fx/defn cache-link-preview-data
(rf/defn cache-link-preview-data
{:events [::cache-link-preview-data]}
[{{:keys [multiaccount]} :db :as cofx} site data]
(multiaccounts.update/optimistic
@@ -90,7 +90,7 @@
(community-link id)
community]))
(fx/defn should-suggest-link-preview
(rf/defn should-suggest-link-preview
{:events [::should-suggest-link-preview]}
[{:keys [db] :as cofx} enabled?]
(multiaccounts.update/multiaccount-update
@@ -98,14 +98,14 @@
:link-preview-request-enabled (boolean enabled?)
{}))
(fx/defn request-link-preview-whitelist
(rf/defn request-link-preview-whitelist
[_]
{::json-rpc/call [{:method "wakuext_getLinkPreviewWhitelist"
:params []
:on-success #(re-frame/dispatch [::link-preview-whitelist-received %])
:on-error #(log/error "Failed to get link preview whitelist")}]})
(fx/defn save-link-preview-whitelist
(rf/defn save-link-preview-whitelist
{:events [::link-preview-whitelist-received]}
[{:keys [db]} whitelist]
{:db (assoc db :link-previews-whitelist
+19 -20
View File
@@ -3,12 +3,11 @@
[status-im.constants :as constants]
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages]
[status-im.utils.fx :as fx]
[utils.re-frame :as rf]
[status-im.chat.models.message-list :as message-list]
[taoensso.timbre :as log]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.notifications-center.core :as notification-center]))
[status-im2.contexts.activity-center.events :as activity-center]))
(defn cursor->clock-value
[^js cursor]
@@ -19,7 +18,7 @@
clock-value
"0x0000000000000000000000000000000000000000000000000000000000000000"))
(fx/defn update-chats-in-app-db
(rf/defn update-chats-in-app-db
{:events [:chats-list/load-success]}
[{:keys [db]} ^js new-chats-js]
(let [{:keys [all-chats chats-home-list]}
@@ -38,20 +37,20 @@
:chats-home-list chats-home-list
:chats/loading? false)}))
(fx/defn load-chat-success
(rf/defn load-chat-success
{:events [:chats-list/load-chat-success]}
[{:keys [db]} ^js chat]
(let [{:keys [chat-id] :as chat} (data-store.chats/<-rpc chat)]
{:db (update-in db [:chats chat-id] merge chat)}))
(fx/defn load-chat
(rf/defn load-chat
[_ chat-id]
{::json-rpc/call [{:method "wakuext_chat"
:params [chat-id]
:on-success #(re-frame/dispatch [:chats-list/load-chat-success %])
:on-error #(log/error "failed to fetch chats" 0 -1 %)}]})
(fx/defn handle-failed-loading-messages
(rf/defn handle-failed-loading-messages
{:events [::failed-loading-messages]}
[{:keys [db]} current-chat-id _ err]
(log/error "failed loading messages" current-chat-id err)
@@ -65,19 +64,19 @@
:unviewed-mentions-count 0
:highlight false))
(fx/defn handle-mark-all-read-successful
(rf/defn handle-mark-all-read-successful
{:events [::mark-all-read-successful]}
[cofx]
(notification-center/get-activity-center-notifications-count cofx))
(activity-center/notifications-fetch-unread-count cofx))
(fx/defn handle-mark-all-read-in-community-successful
(rf/defn handle-mark-all-read-in-community-successful
{:events [::mark-all-read-in-community-successful]}
[{:keys [db] :as cofx} chat-ids]
(fx/merge cofx
(rf/merge cofx
{:db (reduce mark-chat-all-read db chat-ids)}
(notification-center/get-activity-center-notifications-count)))
(activity-center/notifications-fetch-unread-count)))
(fx/defn handle-mark-all-read
(rf/defn handle-mark-all-read
{:events [:chat.ui/mark-all-read-pressed :chat/mark-all-as-read]}
[{db :db} chat-id]
{:db (mark-chat-all-read db chat-id)
@@ -87,7 +86,7 @@
:params [chat-id]
:on-success #(re-frame/dispatch [::mark-all-read-successful])}]})
(fx/defn handle-mark-mark-all-read-in-community
(rf/defn handle-mark-mark-all-read-in-community
{:events [:chat.ui/mark-all-read-in-community-pressed]}
[{db :db} community-id]
(let [community-chat-ids (map #(str community-id %)
@@ -98,7 +97,7 @@
:params [community-id]
:on-success #(re-frame/dispatch [::mark-all-read-in-community-successful %])}]}))
(fx/defn messages-loaded
(rf/defn messages-loaded
"Loads more messages for current chat"
{:events [::messages-loaded]}
[{db :db} chat-id session-id {:keys [cursor messages]}]
@@ -145,7 +144,7 @@
(empty? cursor))
(update :contacts/contacts merge contacts))})))
(fx/defn load-more-messages
(rf/defn load-more-messages
{:events [:chat.ui/load-more-messages]}
[{:keys [db]} chat-id first-request]
(when-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])]
@@ -164,17 +163,17 @@
#(re-frame/dispatch [::messages-loaded chat-id session-id %])
#(re-frame/dispatch [::failed-loading-messages chat-id session-id %]))))))))
(fx/defn load-more-messages-for-current-chat
(rf/defn load-more-messages-for-current-chat
{:events [:chat.ui/load-more-messages-for-current-chat]}
[{:keys [db] :as cofx}]
(load-more-messages cofx (:current-chat-id db) false))
(fx/defn load-messages
(rf/defn load-messages
[{:keys [db now] :as cofx} chat-id]
(when-not (get-in db [:pagination-info chat-id :messages-initialized?])
(fx/merge cofx
(rf/merge cofx
{:db (assoc-in db [:pagination-info chat-id :messages-initialized?] now)
:utils/dispatch-later [{:ms 50 :dispatch [:chat.ui/mark-all-read-pressed chat-id]}
(when-not (get-in cofx [:db :chats chat-id :public?])
{:ms 100 :dispatch [::models.pin-message/load-pin-messages chat-id]})]}
{:ms 100 :dispatch [:pin-message/load-pin-messages chat-id]})]}
(load-more-messages chat-id true))))

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