Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09e922fd05 | ||
|
|
269d13e2f8 | ||
|
|
fc07fbf787 | ||
|
|
621e41e6ab | ||
|
|
3e3a365ffb | ||
|
|
4e6a37fdd0 | ||
|
|
08fb0de7b0 | ||
|
|
f47bc6e40e | ||
|
|
6a05d43b75 | ||
|
|
68ea7cdfc5 | ||
|
|
5693df5a74 | ||
|
|
c7a371c104 | ||
|
|
11d02c1057 | ||
|
|
c13be92505 | ||
|
|
ae10908984 | ||
|
|
53ef1ef4b5 | ||
|
|
bd84a36582 | ||
|
|
9be8a5b961 | ||
|
|
5ca46fe805 | ||
|
|
2f1c236333 | ||
|
|
f2fa3a1886 | ||
|
|
e6628bd346 | ||
|
|
6e272a96c8 | ||
|
|
da0f0d3a81 | ||
|
|
f7af7ca25d | ||
|
|
f3ad8beb6d | ||
|
|
2e68fb22f5 | ||
|
|
f18044c9dc | ||
|
|
5947769881 | ||
|
|
d4897de205 | ||
|
|
d948939ce3 | ||
|
|
1000cb79a2 | ||
|
|
f55b646c20 | ||
|
|
bc7578ae85 | ||
|
|
49e9738ff8 | ||
|
|
9e1412007f | ||
|
|
c198133769 | ||
|
|
6d5ff4aa92 | ||
|
|
0e614d51ef | ||
|
|
42de2a6384 | ||
|
|
3d71fdba98 | ||
|
|
9de4417f03 | ||
|
|
9e7db45d08 | ||
|
|
14c243803f | ||
|
|
e61889f16c | ||
|
|
1230594046 | ||
|
|
3fc1ba357e | ||
|
|
3152cf0aa3 | ||
|
|
c6e8aad745 | ||
|
|
bab0fc7ac0 | ||
|
|
32d85d5059 | ||
|
|
a9295ac17e | ||
|
|
7dccbf2395 |
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"module:metro-react-native-babel-preset"
|
||||
],
|
||||
"plugins": [
|
||||
"react-native-reanimated/plugin"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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]}}}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
#--------------
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
library 'status-jenkins-lib@v1.6.3'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
library 'status-jenkins-lib@v1.6.3'
|
||||
|
||||
pipeline {
|
||||
agent { label params.AGENT_LABEL }
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
library 'status-jenkins-lib@v1.6.3'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
library 'status-jenkins-lib@v1.6.3'
|
||||
|
||||
pipeline {
|
||||
agent { label 'macos' }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
library 'status-jenkins-lib@v1.6.3'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
];
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 469 B |
|
After Width: | Height: | Size: 758 B |
|
After Width: | Height: | Size: 525 B |
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 924 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 738 KiB |
@@ -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}}}}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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}]])]]]))))
|
||||
|
||||
@@ -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))))
|
||||
@@ -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
|
||||
|
||||
@@ -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))))
|
||||
@@ -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}]])]]]))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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}])]))
|
||||
@@ -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}]]]]))]))
|
||||
|
||||
@@ -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)))))
|
||||
@@ -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
|
||||
|
||||
@@ -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}))
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]))
|
||||
@@ -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))
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)})
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))))))
|
||||
@@ -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})
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.utils.fx :as fx]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.contact.db :as contact.db]
|
||||
[status-im.utils.platform :as platform]
|
||||
[taoensso.timbre :as log]
|
||||
@@ -489,7 +489,7 @@
|
||||
(assoc-in [last-idx :to] (dec (count text)))
|
||||
(assoc-in [last-idx :checked?] false)))))))
|
||||
|
||||
(fx/defn on-text-input
|
||||
(rf/defn on-text-input
|
||||
{:events [::on-text-input]}
|
||||
[{:keys [db]} {:keys [previous-text start end] :as args}]
|
||||
(log/debug "[mentions] on-text-input args" args)
|
||||
@@ -533,7 +533,7 @@
|
||||
[[:text (subs text 0 first-from)]]))
|
||||
idxs))))
|
||||
|
||||
(fx/defn recheck-at-idxs
|
||||
(rf/defn recheck-at-idxs
|
||||
[{:keys [db]} mentionable-users]
|
||||
(let [chat-id (:current-chat-id db)
|
||||
text (get-in db [:chat/inputs chat-id :input-text])
|
||||
@@ -551,7 +551,7 @@
|
||||
:at-idxs new-at-idxs)
|
||||
(assoc-in [:chat/inputs-with-mentions chat-id] calculated-input))}))
|
||||
|
||||
(fx/defn calculate-suggestions
|
||||
(rf/defn calculate-suggestions
|
||||
{:events [::calculate-suggestions]}
|
||||
[{:keys [db]} mentionable-users]
|
||||
(let [chat-id (:current-chat-id db)
|
||||
@@ -616,31 +616,31 @@
|
||||
" "))
|
||||
(subs text mention-end)])))
|
||||
|
||||
(fx/defn clear-suggestions
|
||||
(rf/defn clear-suggestions
|
||||
[{:keys [db]}]
|
||||
(log/debug "[mentions] clear suggestions")
|
||||
(let [chat-id (:current-chat-id db)]
|
||||
{:db (update db :chats/mention-suggestions dissoc chat-id)}))
|
||||
|
||||
(fx/defn clear-mentions
|
||||
(rf/defn clear-mentions
|
||||
[{:keys [db] :as cofx}]
|
||||
(log/debug "[mentions] clear mentions")
|
||||
(let [chat-id (:current-chat-id db)]
|
||||
(fx/merge
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (-> db
|
||||
(update-in [:chats/mentions chat-id] dissoc :mentions)
|
||||
(update :chat/inputs-with-mentions dissoc chat-id))}
|
||||
(clear-suggestions))))
|
||||
|
||||
(fx/defn clear-cursor
|
||||
(rf/defn clear-cursor
|
||||
{:events [::clear-cursor]}
|
||||
[{:keys [db]}]
|
||||
(log/debug "[mentions] clear cursor")
|
||||
{:db
|
||||
(update db :chats/cursor dissoc (:current-chat-id db))})
|
||||
|
||||
(fx/defn check-selection
|
||||
(rf/defn check-selection
|
||||
{:events [::on-selection-change]}
|
||||
[{:keys [db] :as cofx}
|
||||
{:keys [start end] :as selection}
|
||||
@@ -655,7 +655,7 @@
|
||||
(<= (dec end) to))
|
||||
idx))
|
||||
at-idxs)
|
||||
(fx/merge
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (update-in db [:chats/mentions chat-id :mentions]
|
||||
assoc
|
||||
@@ -673,7 +673,7 @@
|
||||
(rn/find-node-handle (react/current-ref ref))
|
||||
cursor))))
|
||||
|
||||
(fx/defn reset-text-input-cursor
|
||||
(rf/defn reset-text-input-cursor
|
||||
[_ ref cursor]
|
||||
{::reset-text-input-cursor [ref cursor]})
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
(ns status-im.chat.models.message
|
||||
(:require [status-im.chat.models :as chat-model]
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.delete-message :as delete-message]
|
||||
[status-im.chat.models.loading :as chat.loading]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.messages :as data-store.messages]
|
||||
[status-im.ethereum.json-rpc :as json-rpc]
|
||||
[status-im.transport.message.protocol :as protocol]
|
||||
[status-im.utils.fx :as fx]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.ui.screens.chat.state :as view.state]
|
||||
[status-im.chat.models.loading :as chat.loading]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.gfycat.core :as gfycat]))
|
||||
[status-im.utils.types :as types]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn- message-loaded?
|
||||
[db chat-id message-id]
|
||||
@@ -35,7 +36,7 @@
|
||||
;; TODO this is too expensive, probably we could mark message somehow and just hide it in the UI
|
||||
(message-list/rebuild-message-list {:db (update-in db [:messages chat-id] dissoc message-id)} chat-id))
|
||||
|
||||
(fx/defn add-senders-to-chat-users
|
||||
(rf/defn add-senders-to-chat-users
|
||||
{:events [:chat/add-senders-to-chat-users]}
|
||||
[{:keys [db]} messages]
|
||||
(reduce (fn [acc {:keys [chat-id alias name identicon from]}]
|
||||
@@ -154,66 +155,60 @@
|
||||
(update-in [:message-lists constants/timeline-chat-id] message-list/add message)))
|
||||
db)))
|
||||
|
||||
(fx/defn process-statuses
|
||||
(rf/defn process-statuses
|
||||
{:events [:process-statuses]}
|
||||
[{:keys [db]} statuses]
|
||||
{:db (reduce reduce-js-statuses db statuses)})
|
||||
|
||||
(fx/defn update-db-message-status
|
||||
(rf/defn update-db-message-status
|
||||
[{:keys [db] :as cofx} chat-id message-id status]
|
||||
(when (get-in db [:messages chat-id message-id])
|
||||
(fx/merge cofx
|
||||
(rf/merge cofx
|
||||
{:db (assoc-in db
|
||||
[:messages chat-id message-id :outgoing-status]
|
||||
status)})))
|
||||
|
||||
(fx/defn update-message-status
|
||||
(rf/defn update-message-status
|
||||
[{:keys [db] :as cofx} chat-id message-id status]
|
||||
(fx/merge cofx
|
||||
(rf/merge cofx
|
||||
(update-db-message-status chat-id message-id status)))
|
||||
|
||||
(fx/defn resend-message
|
||||
(rf/defn resend-message
|
||||
[{:keys [db] :as cofx} chat-id message-id]
|
||||
(fx/merge cofx
|
||||
(rf/merge cofx
|
||||
{::json-rpc/call [{:method "wakuext_reSendChatMessage"
|
||||
:params [message-id]
|
||||
:on-success #(log/debug "re-sent message successfully")
|
||||
:on-error #(log/error "failed to re-send message" %)}]}
|
||||
(update-message-status chat-id message-id :sending)))
|
||||
|
||||
(fx/defn delete-message
|
||||
"Deletes chat message, rebuild message-list"
|
||||
{:events [:chat.ui/delete-message]}
|
||||
[{:keys [db] :as cofx} chat-id message-id]
|
||||
(fx/merge cofx
|
||||
{:db (update-in db [:messages chat-id] dissoc message-id)}
|
||||
(data-store.messages/delete-message message-id)
|
||||
(message-list/rebuild-message-list chat-id)))
|
||||
|
||||
(fx/defn send-message
|
||||
(rf/defn send-message
|
||||
[cofx message]
|
||||
(protocol/send-chat-messages cofx [message]))
|
||||
|
||||
(fx/defn send-messages
|
||||
(rf/defn send-messages
|
||||
[cofx messages]
|
||||
(protocol/send-chat-messages cofx messages))
|
||||
|
||||
(fx/defn handle-removed-messages
|
||||
(rf/defn handle-removed-messages
|
||||
{:events [::handle-removed-messages]}
|
||||
[{:keys [db] :as cofx} removed-messages]
|
||||
(let [mark-as-seen-fx (mapv (fn [removed-message]
|
||||
(let [chat-id (:chatId removed-message)
|
||||
message-id (:messageId removed-message)]
|
||||
(data-store.messages/mark-messages-seen chat-id
|
||||
[message-id]
|
||||
#(re-frame/dispatch [:chat/decrease-unviewed-count chat-id %3])))) removed-messages)
|
||||
(let [mark-as-deleted-fx (->> removed-messages
|
||||
(map #(assoc % :message-id (:messageId %)))
|
||||
(group-by :chatId)
|
||||
(mapv (fn [[chat-id messages]] (delete-message/delete-messages-localy messages chat-id))))
|
||||
mark-as-seen-fx (mapv (fn [removed-message]
|
||||
(let [chat-id (:chatId removed-message)
|
||||
message-id (:messageId removed-message)]
|
||||
(data-store.messages/mark-messages-seen chat-id
|
||||
[message-id]
|
||||
#(re-frame/dispatch [:chat/decrease-unviewed-count chat-id %3]))))
|
||||
removed-messages)
|
||||
remove-messages-fx (fn [{:keys [db]}]
|
||||
{:db (reduce (fn [acc current]
|
||||
(update-in acc [:messages (:chatId current)] dissoc (:messageId current)))
|
||||
db removed-messages)
|
||||
:dispatch-n [[:get-activity-center-notifications]
|
||||
[:get-activity-center-notifications-count]]})]
|
||||
(apply fx/merge cofx (conj mark-as-seen-fx remove-messages-fx))))
|
||||
{:dispatch [:activity-center.notifications/fetch-unread-count]})]
|
||||
(apply rf/merge cofx (-> mark-as-deleted-fx
|
||||
(concat mark-as-seen-fx)
|
||||
(conj remove-messages-fx)))))
|
||||
|
||||
(comment
|
||||
(handle-removed-messages
|
||||
@@ -228,7 +223,7 @@
|
||||
(<= message-clock cleared-at))
|
||||
messages)))
|
||||
|
||||
(fx/defn handle-cleared-histories-messages
|
||||
(rf/defn handle-cleared-histories-messages
|
||||
{:events [::handle-cleared-hisotories-messages]}
|
||||
[{:keys [db]} cleared-histories]
|
||||
{:db (reduce (fn [acc current]
|
||||
|
||||