Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9be30f69e4 | ||
|
|
62c6a0ea72 | ||
|
|
7f54427df4 | ||
|
|
ac9fb2fe4d | ||
|
|
509b8bf3e1 | ||
|
|
a74a44e492 | ||
|
|
af3ba74e30 | ||
|
|
5492d502fc | ||
|
|
7368478033 | ||
|
|
bc050af9ed | ||
|
|
d6a8f912ea | ||
|
|
6a23f64550 | ||
|
|
4bbacf542c | ||
|
|
6991f213af | ||
|
|
35eae4587b | ||
|
|
46fd30597a | ||
|
|
af5f979443 | ||
|
|
adfb0ddb5d | ||
|
|
0a282b3a47 | ||
|
|
5f98e96d23 | ||
|
|
823fce2457 | ||
|
|
1d5e1479f7 | ||
|
|
c2c281d9c6 | ||
|
|
1f6ebbb5e8 | ||
|
|
73983b2abd | ||
|
|
390ac858dc | ||
|
|
69c49a5557 | ||
|
|
2ed3b50663 | ||
|
|
8dd75205f3 | ||
|
|
081f1a2115 |
@@ -36,6 +36,8 @@ endif
|
||||
export TMPDIR = /tmp/tmp-status-mobile-$(BUILD_TAG)
|
||||
# This has to be specified for both the Node.JS server process and the Qt process.
|
||||
export REACT_SERVER_PORT ?= 5001
|
||||
# Fix for ERR_OSSL_EVP_UNSUPPORTED error.
|
||||
export NODE_OPTIONS += --openssl-legacy-provider
|
||||
# The path can be anything, but home is usually safest.
|
||||
export KEYSTORE_PATH ?= $(HOME)/.gradle/status-im.keystore
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
pipeline {
|
||||
agent { label params.AGENT_LABEL }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
pipeline {
|
||||
agent { label 'macos' }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.5.7'
|
||||
library 'status-jenkins-lib@v1.6.0'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -14,14 +14,25 @@ This step will take a while the first time as it will download all dependencies.
|
||||
|
||||
There are three steps necessary to start development, in this case for Android:
|
||||
|
||||
1. `make run-clojure` - Compiles Clojure into JavaScript, watches for changes on cljs files, and hot-reloads code in the app
|
||||
2. `make run-metro` - Starts metro bundler and watches JavaScript code
|
||||
3. `make run-android` - Builds the Android app and starts it on the device
|
||||
1. `make run-clojure` - Compiles Clojure into JavaScript, watches for changes on cljs files, and hot-reloads code in the app.
|
||||
2. `make run-metro` - Starts metro bundler and watches JavaScript code.
|
||||
3. `make run-android` or `make run-ios` - Builds the Android/iOS app and starts it on the device.
|
||||
|
||||
The first two will continue watching for changes and keep re-building the app. They need to be ready first.
|
||||
The last one will exit once the app is up and ready.
|
||||
|
||||
You need to have your emulator or real devices running and visible to adb, before you run `make run-android`.
|
||||
## Simulators and Devices
|
||||
### Android
|
||||
|
||||
You need to have an emulator like [AVD](https://developer.android.com/studio/run/emulator), or [Genymotion](#genymotion-virtualization), or a real device running and visible to [adb](https://developer.android.com/studio/command-line/adb), before you run `make run-android`.
|
||||
|
||||
### iOS
|
||||
|
||||
You can specify the simulator type by adding the `SIMULATOR` flag:
|
||||
```sh
|
||||
make run-ios SIMULATOR="iPhone 11 Pro"
|
||||
```
|
||||
Some manual steps are necesary for [developing on a physical iOS Device](#physical-ios-device).
|
||||
|
||||
# Build release
|
||||
|
||||
@@ -71,3 +82,17 @@ Steps:
|
||||
2. [Setup Git to use your GPG key](https://help.github.com/en/github/authenticating-to-github/telling-git-about-your-signing-key)
|
||||
3. [Setup Git to sign commits](https://help.github.com/en/github/authenticating-to-github/signing-commits)
|
||||
4. [Setup GitHub to validate commits](https://help.github.com/en/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account)
|
||||
|
||||
## Physical iOS Device
|
||||
|
||||
To use a physical iPhone your device UDID must be added to provisioning profiles and your Apple account invited as Developer to Status team.
|
||||
|
||||
1. [Get your UDID of your iPhone.](https://www.extentia.com/post/finding-the-udid-of-an-ios-device)
|
||||
2. Request from someone with access like @cammellos or @jakubgs to
|
||||
- Add the UDID to development devices on Apple Developer Portal.
|
||||
- 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.
|
||||
|
||||
Once build finishes Status should start on your iPhone with its logs in terminal running `make run-metro`.
|
||||
|
||||
@@ -8,8 +8,8 @@ GEM
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.622.0)
|
||||
aws-sdk-core (3.136.0)
|
||||
aws-partitions (1.644.0)
|
||||
aws-sdk-core (3.159.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
@@ -21,7 +21,7 @@ GEM
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.1)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
@@ -36,7 +36,7 @@ GEM
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.92.4)
|
||||
excon (0.93.0)
|
||||
faraday (1.10.2)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
@@ -66,7 +66,7 @@ GEM
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.209.1)
|
||||
fastlane (2.210.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -109,9 +109,9 @@ GEM
|
||||
fastlane-plugin-diawi (2.1.0)
|
||||
rest-client (>= 2.0.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.25.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-core (0.7.0)
|
||||
google-apis-androidpublisher_v3 (0.29.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-core (0.9.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -120,23 +120,23 @@ GEM
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.13.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.10.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-storage_v1 (0.17.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-iamcredentials_v1 (0.15.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.11.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.39.0)
|
||||
google-cloud-errors (1.3.0)
|
||||
google-cloud-storage (1.43.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.17.0)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
@@ -230,4 +230,4 @@ DEPENDENCIES
|
||||
fastlane-plugin-diawi
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.9
|
||||
2.3.20
|
||||
|
||||
@@ -45,10 +45,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "1py4q91ll3v7ylcqflgd190y40d3ixgrhpln011gr5adgbzg9hr6";
|
||||
sha256 = "0x58gl654kcx65wyrix5swh5f9y431881a5mql8g3zwshd81fyb3";
|
||||
type = "gem";
|
||||
};
|
||||
version = "1.622.0";
|
||||
version = "1.644.0";
|
||||
};
|
||||
aws-sdk-core = {
|
||||
dependencies = ["aws-eventstream" "aws-partitions" "aws-sigv4" "jmespath"];
|
||||
@@ -56,10 +56,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "111zdfl6p1n949rvsfr0c88k9yzpibrcd8fihyshbibc2w06qj2b";
|
||||
sha256 = "0cdjmpblskjmhag7wx6scwkd3cw1gfh85syr599s05k8zp6y4qw8";
|
||||
type = "gem";
|
||||
};
|
||||
version = "3.136.0";
|
||||
version = "3.159.0";
|
||||
};
|
||||
aws-sdk-kms = {
|
||||
dependencies = ["aws-sdk-core" "aws-sigv4"];
|
||||
@@ -89,10 +89,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "1d4bifmll4hrf4gihr5hdvn59wjpz4qpyg5jj95kp17fykzqg36n";
|
||||
sha256 = "11hkna2av47bl0yprgp8k4ya70rc3m2ib5w10fn0piplgkkmhz7m";
|
||||
type = "gem";
|
||||
};
|
||||
version = "1.5.1";
|
||||
version = "1.5.2";
|
||||
};
|
||||
babosa = {
|
||||
groups = ["default"];
|
||||
@@ -213,10 +213,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "0cdc76kgr4f1mq4jwbmq1qvr9c15hb4r1cx4dvrdra13vy9sckb5";
|
||||
sha256 = "0b3rfqy87yiv9xmh260nyddxxjqj0vy32xvajvyn5jnjx96jwa24";
|
||||
type = "gem";
|
||||
};
|
||||
version = "0.92.4";
|
||||
version = "0.93.0";
|
||||
};
|
||||
faraday = {
|
||||
dependencies = ["faraday-em_http" "faraday-em_synchrony" "faraday-excon" "faraday-httpclient" "faraday-multipart" "faraday-net_http" "faraday-net_http_persistent" "faraday-patron" "faraday-rack" "faraday-retry" "ruby2_keywords"];
|
||||
@@ -368,10 +368,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "17m4nrpsjx2kadgy3qasis9d8rzajxgi9gzwkvdj7c0jjs6a768d";
|
||||
sha256 = "1wxzcs81c5ji30hrz64884rg0w56v2nwjyiyc8daka578bg7s8d6";
|
||||
type = "gem";
|
||||
};
|
||||
version = "2.209.1";
|
||||
version = "2.210.1";
|
||||
};
|
||||
fastlane-plugin-clean_testflight_testers = {
|
||||
groups = ["default"];
|
||||
@@ -410,10 +410,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "0psz3w8c95ashk5hlfvn5l32mg111z7fv07ngvvgm5mkw6wksh4d";
|
||||
sha256 = "0z2i5wpkawkf4f42i55b1240iw8819wx828579s4vavhd9s17yik";
|
||||
type = "gem";
|
||||
};
|
||||
version = "0.25.0";
|
||||
version = "0.29.0";
|
||||
};
|
||||
google-apis-core = {
|
||||
dependencies = ["addressable" "googleauth" "httpclient" "mini_mime" "representable" "retriable" "rexml" "webrick"];
|
||||
@@ -421,10 +421,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "07jhk74awjc1npf2rim4kmwz4v0n3ny6y5g31saq7aqxkjmlp3hd";
|
||||
sha256 = "1w9m4zc5xswz2h7gj4jvnb1ivzb6lcsl75fnw8ip7qz6hzwfgrlc";
|
||||
type = "gem";
|
||||
};
|
||||
version = "0.7.0";
|
||||
version = "0.9.0";
|
||||
};
|
||||
google-apis-iamcredentials_v1 = {
|
||||
dependencies = ["google-apis-core"];
|
||||
@@ -432,10 +432,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "0mf9l1r28hxqqh2ilza2pfffy32vvzq10ifqf88a732k16iwvin1";
|
||||
sha256 = "06smnmn2s460xl9x9rh07a3fkqdrjjy6azmx8iywggqgv2k5d8p9";
|
||||
type = "gem";
|
||||
};
|
||||
version = "0.13.0";
|
||||
version = "0.15.0";
|
||||
};
|
||||
google-apis-playcustomapp_v1 = {
|
||||
dependencies = ["google-apis-core"];
|
||||
@@ -443,10 +443,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "0jgmfayhww7sy06pzf8r7bvxjix4jdazbyyy4mhp6ghv1s9r85w7";
|
||||
sha256 = "00nfh3xh51mbh02f8nqlsijdh59k9vjs5sglvpv09pl3wnhirf7w";
|
||||
type = "gem";
|
||||
};
|
||||
version = "0.10.0";
|
||||
version = "0.11.0";
|
||||
};
|
||||
google-apis-storage_v1 = {
|
||||
dependencies = ["google-apis-core"];
|
||||
@@ -454,10 +454,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "19ccrdgb34w49l62p28yy5qrgwgb4wxs9q3mnb2d334q4q3wsk9f";
|
||||
sha256 = "17qamcjnf22zvw1g169g8a2gkzdsxx4ij3a4ganihyrcf9r62asj";
|
||||
type = "gem";
|
||||
};
|
||||
version = "0.17.0";
|
||||
version = "0.19.0";
|
||||
};
|
||||
google-cloud-core = {
|
||||
dependencies = ["google-cloud-env" "google-cloud-errors"];
|
||||
@@ -486,10 +486,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "0nakfswnck6grjpyhckzl40qccyys3sy999h5axk0rldx96fnivd";
|
||||
sha256 = "0jynh1s93nl8njm5l5wcy86pnjmv112cq6m0443s52f04hg6h2s5";
|
||||
type = "gem";
|
||||
};
|
||||
version = "1.2.0";
|
||||
version = "1.3.0";
|
||||
};
|
||||
google-cloud-storage = {
|
||||
dependencies = ["addressable" "digest-crc" "google-apis-iamcredentials_v1" "google-apis-storage_v1" "google-cloud-core" "googleauth" "mini_mime"];
|
||||
@@ -497,10 +497,10 @@
|
||||
platforms = [];
|
||||
source = {
|
||||
remotes = ["https://rubygems.org"];
|
||||
sha256 = "030y55cybc5w538bdd2xxjqwfka8fnkhv7ld4iqmn7byrkgy9dxc";
|
||||
sha256 = "0j3hqyb4zgj6az6p0bz0kgskl6fddrwb095kxfbdx8r11m07mfr8";
|
||||
type = "gem";
|
||||
};
|
||||
version = "1.39.0";
|
||||
version = "1.43.0";
|
||||
};
|
||||
googleauth = {
|
||||
dependencies = ["faraday" "jwt" "memoist" "multi_json" "os" "signet"];
|
||||
|
||||
@@ -238,7 +238,7 @@ PODS:
|
||||
- React
|
||||
- react-native-status (1.0.0):
|
||||
- React
|
||||
- react-native-status-keycard (2.5.37):
|
||||
- react-native-status-keycard (2.5.38):
|
||||
- Keycard
|
||||
- React
|
||||
- react-native-webview (11.16.0):
|
||||
@@ -640,7 +640,7 @@ SPEC CHECKSUMS:
|
||||
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
|
||||
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
|
||||
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
|
||||
glog: 64a900d72fb14cd1b2cdf3b717a5555ceb889d49
|
||||
glog: 36ce0530c6d2c3a5a4326885ef4069564887a1db
|
||||
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
|
||||
Keycard: ac6df4d91525c3c82635ac24d4ddd9a80aca5fc8
|
||||
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
|
||||
@@ -671,7 +671,7 @@ SPEC CHECKSUMS:
|
||||
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
|
||||
react-native-slider: 12bd76d3d568c9c5500825db54123d44b48e4ad4
|
||||
react-native-status: 21f75d492fd311dc111303da38a7a2b23a8a8466
|
||||
react-native-status-keycard: 961d01ca190889ddf220206822fd752f8f4f3f7a
|
||||
react-native-status-keycard: f60ca57d789aad6875c64ece81ab06ef0609e0d3
|
||||
react-native-webview: 28a8636d97ee641f2ee8f20492d7a6c269c1d703
|
||||
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
|
||||
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
|
||||
|
||||
@@ -164,9 +164,22 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
|
||||
NSString *networkDirPath = @"ethereum/mainnet_rpc";
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
NSString *goerliNetworkDirPath = @"ethereum/goerli_rpc_dev";
|
||||
#else
|
||||
NSString *goerliNetworkDirPath = @"ethereum/goerli_rpc";
|
||||
#endif
|
||||
|
||||
NSURL *networkDir = [rootUrl URLByAppendingPathComponent:networkDirPath];
|
||||
NSURL *originalGethLogsFile = [networkDir URLByAppendingPathComponent:@"geth.log"];
|
||||
NSURL *gethLogsFile = [logsFolderName URLByAppendingPathComponent:@"geth.log"];
|
||||
NSURL *gethLogsFile = [logsFolderName URLByAppendingPathComponent:@"mainnet_geth.log"];
|
||||
|
||||
NSURL *goerliNetworkDir = [rootUrl URLByAppendingPathComponent:goerliNetworkDirPath];
|
||||
NSURL *goerliGethLogsFile = [goerliNetworkDir URLByAppendingPathComponent:@"geth.log"];
|
||||
NSURL *goerliLogsFile = [logsFolderName URLByAppendingPathComponent:@"goerli_geth.log"];
|
||||
|
||||
NSURL *mainGethLogsFile = [rootUrl URLByAppendingPathComponent:@"geth.log"];
|
||||
NSURL *mainLogsFile = [logsFolderName URLByAppendingPathComponent:@"geth.log"];
|
||||
|
||||
[dbJson writeToFile:dbFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
[jsLogs writeToFile:jsLogsFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
@@ -174,6 +187,8 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
|
||||
//NSString* gethLogs = StatusgoExportNodeLogs();
|
||||
//[gethLogs writeToFile:gethLogsFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
[fileManager copyItemAtPath:originalGethLogsFile.path toPath:gethLogsFile.path error:nil];
|
||||
[fileManager copyItemAtPath:goerliGethLogsFile.path toPath:goerliLogsFile.path error:nil];
|
||||
[fileManager copyItemAtPath:mainGethLogsFile.path toPath:mainLogsFile.path error:nil];
|
||||
|
||||
[SSZipArchive createZipFileAtPath:zipFile.path withContentsOfDirectory:logsFolderName.path];
|
||||
[fileManager removeItemAtPath:logsFolderName.path error:nil];
|
||||
@@ -411,12 +426,10 @@ RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
|
||||
NSString *relativeDataDir = [configJSON objectForKey:@"DataDir"];
|
||||
NSString *absDataDir = [rootUrl.path stringByAppendingString:relativeDataDir];
|
||||
NSURL *absDataDirUrl = [NSURL fileURLWithPath:absDataDir];
|
||||
NSURL *dataDirUrl = [NSURL fileURLWithPath:relativeDataDir];
|
||||
NSURL *logUrl = [dataDirUrl URLByAppendingPathComponent:@"geth.log"];
|
||||
NSString *keystoreDir = [@"/keystore/" stringByAppendingString:keyUID];
|
||||
[configJSON setValue:keystoreDir forKey:@"KeyStoreDir"];
|
||||
[configJSON setValue:@"" forKey:@"LogDir"];
|
||||
[configJSON setValue:logUrl.path forKey:@"LogFile"];
|
||||
[configJSON setValue:@"geth.log" forKey:@"LogFile"];
|
||||
|
||||
NSString *resultingConfig = [configJSON bv_jsonStringWithPrettyPrint:NO];
|
||||
NSLog(@"node config %@", resultingConfig);
|
||||
@@ -426,7 +439,7 @@ RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
|
||||
withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
}
|
||||
|
||||
NSLog(@"logUrlPath %@ rootDir %@", logUrl.path, rootUrl.path);
|
||||
NSLog(@"logUrlPath %@ rootDir %@", @"geth.log", rootUrl.path);
|
||||
NSURL *absLogUrl = [absDataDirUrl URLByAppendingPathComponent:@"geth.log"];
|
||||
if(![fileManager fileExistsAtPath:absLogUrl.path]) {
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
@@ -72,6 +72,9 @@ in stdenv.mkDerivation rec {
|
||||
ANDROID_SDK_ROOT = "${androidPkgs.sdk}";
|
||||
ANDROID_NDK_ROOT = "${androidPkgs.ndk}";
|
||||
|
||||
# Fix for ERR_OSSL_EVP_UNSUPPORTED error.
|
||||
NODE_OPTIONS = "--openssl-legacy-provider";
|
||||
|
||||
# Used by the Android Gradle build script in android/build.gradle
|
||||
STATUS_GO_ANDROID_LIBDIR = status-go;
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
let
|
||||
inherit (lib) catAttrs unique;
|
||||
|
||||
# Sub-shells preparing various dependencies.
|
||||
nodejs-sh = callPackage ./shells/nodejs.nix { };
|
||||
cocoapods-sh = callPackage ./shells/pod.nix { };
|
||||
bundler-sh = callPackage ./shells/bundler.nix { };
|
||||
cocoapods-sh = callPackage ./shells/cocoapods.nix { };
|
||||
status-go-sh = callPackage ./shells/status-go.nix { inherit status-go; };
|
||||
|
||||
in {
|
||||
@@ -13,7 +15,7 @@ in {
|
||||
|
||||
shell = mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
xcodeWrapper watchman bundler procps
|
||||
xcodeWrapper watchman procps
|
||||
flock # used in nix/scripts/node_modules.sh
|
||||
];
|
||||
|
||||
@@ -21,7 +23,8 @@ in {
|
||||
inputsFrom = [
|
||||
fastlane.shell
|
||||
cocoapods-sh
|
||||
nodejs-sh # before 'pod install'
|
||||
nodejs-sh # before 'pod install'
|
||||
bundler-sh # before 'pod install'
|
||||
status-go-sh # before 'pod install'
|
||||
];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{ mkShell, bundler }:
|
||||
|
||||
mkShell {
|
||||
buildInputs = [ bundler ];
|
||||
shellHook = ''
|
||||
bundle install --quiet --gemfile=fastlane/Gemfile
|
||||
'';
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{ mkShell, cocoapods }:
|
||||
|
||||
let
|
||||
# source of what pod should install
|
||||
podfileLock = "ios/Podfile.lock";
|
||||
# current state of pods installed by pod
|
||||
manifestLock = "ios/Pods/Manifest.lock";
|
||||
in mkShell {
|
||||
buildInputs = [ cocoapods ];
|
||||
shellHook = ''
|
||||
pushd "$STATUS_MOBILE_HOME" > /dev/null
|
||||
{
|
||||
echo "Checking for modifications in ios/Pods..."
|
||||
if diff -q ${podfileLock} ${manifestLock}; then
|
||||
echo "No modifications detected."
|
||||
else
|
||||
# CocoaPods are trash and can't handle other pod instances running
|
||||
./scripts/wait-for.sh 240 'pod install'
|
||||
(cd ios && pod install)
|
||||
fi
|
||||
}
|
||||
popd > /dev/null
|
||||
'';
|
||||
}
|
||||
@@ -33,18 +33,19 @@ in {
|
||||
gradlePropParser = callPackage ./tools/gradlePropParser.nix { };
|
||||
|
||||
# Package version adjustments
|
||||
gradle = super.pkgs.gradle_5;
|
||||
nodejs = super.pkgs.nodejs-16_x;
|
||||
openjdk = super.pkgs.openjdk8_headless;
|
||||
gradle = super.gradle_5;
|
||||
nodejs = super.nodejs-16_x;
|
||||
yarn = super.yarn.override { nodejs = super.nodejs-16_x; };
|
||||
openjdk = super.openjdk8_headless;
|
||||
xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } {
|
||||
version = "13.3";
|
||||
allowHigher = true;
|
||||
};
|
||||
go = super.pkgs.go_1_17;
|
||||
buildGoPackage = super.pkgs.buildGo117Package;
|
||||
buildGoModule = super.pkgs.buildGo117Module;
|
||||
go = super.go_1_18;
|
||||
buildGoPackage = super.buildGo118Package;
|
||||
buildGoModule = super.buildGo118Module;
|
||||
gomobile = (super.gomobile.overrideAttrs (old: {
|
||||
patches = self.pkgs.fetchurl { # https://github.com/golang/mobile/pull/84
|
||||
patches = self.fetchurl { # https://github.com/golang/mobile/pull/84
|
||||
url = "https://github.com/golang/mobile/commit/f20e966e05b8f7e06bed500fa0da81cf6ebca307.patch";
|
||||
sha256 = "sha256-TZ/Yhe8gMRQUZFAs9G5/cf2b9QGtTHRSObBFD5Pbh7Y=";
|
||||
};
|
||||
|
||||
@@ -13,8 +13,8 @@ let
|
||||
name = "nixpkgs-source";
|
||||
owner = "NixOS";
|
||||
repo = "nixpkgs";
|
||||
rev = "280d485b6f94d5ee8645aaf098c61a5def5df2be";
|
||||
sha256 = "sha256-8ZWWJEDypkTCrzIWUxpin7CS2M3uPjjbl/8mpcVZQ9I";
|
||||
rev = "579238da5f431b7833a9f0681663900aaf0dd1e8";
|
||||
sha256 = "sha256-cDwASlAf/h0fsHtDm9yNBHEHK0uq6do+mIUEgh1i5yg=";
|
||||
# To get the compressed Nix sha256, use:
|
||||
# nix-prefetch-url --unpack https://github.com/${ORG}/nixpkgs/archive/${REV}.tar.gz
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# by setting android_sdk.accept_license = true.
|
||||
androidenv.composeAndroidPackages {
|
||||
toolsVersion = "26.1.1";
|
||||
platformToolsVersion = "33.0.1";
|
||||
platformToolsVersion = "33.0.2";
|
||||
buildToolsVersions = [ "31.0.0" ];
|
||||
platformVersions = [ "31" ];
|
||||
cmakeVersions = [ "3.18.1" ];
|
||||
|
||||
@@ -27,13 +27,11 @@
|
||||
"@walletconnect/client": "^2.0.0-beta.23",
|
||||
"@walletconnect/client-legacy": "npm:@walletconnect/client@^1.7.1",
|
||||
"bignumber.js": "git+https://github.com/status-im/bignumber.js.git#refs/tags/v4.0.2-status",
|
||||
"buffer": "^5.4.2",
|
||||
"chance": "^1.1.0",
|
||||
"create-react-class": "^15.6.2",
|
||||
"emojilib": "^2.4.0",
|
||||
"eth-phishing-detect": "^1.1.13",
|
||||
"eth-phishing-detect": "^1.2.0",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"hi-base32": "^0.5.0",
|
||||
"i18n-js": "^3.3.0",
|
||||
"node-libs-react-native": "^1.2.1",
|
||||
"qrcode": "^1.4.1",
|
||||
@@ -73,7 +71,6 @@
|
||||
"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",
|
||||
"rn-emoji-keyboard": "git+https://github.com/status-im/rn-emoji-keyboard.git#refs/tags/v0.4.3",
|
||||
"tdigest": "^0.1.1",
|
||||
"web3-utils": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
|
After Width: | Height: | Size: 321 B |
|
After Width: | Height: | Size: 373 B |
|
After Width: | Height: | Size: 307 B |
|
After Width: | Height: | Size: 387 B |
|
After Width: | Height: | Size: 334 B |
|
After Width: | Height: | Size: 447 B |
|
After Width: | Height: | Size: 454 B |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 681 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 667 B |
|
After Width: | Height: | Size: 940 B |
|
After Width: | Height: | Size: 371 B |
|
After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 814 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 441 B |
|
After Width: | Height: | Size: 585 B |
|
After Width: | Height: | Size: 580 B |
|
After Width: | Height: | Size: 892 B |
|
After Width: | Height: | Size: 327 B |
|
After Width: | Height: | Size: 440 B |
|
After Width: | Height: | Size: 249 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
@@ -248,6 +248,8 @@
|
||||
|
||||
(def react-native-gradien #js {:default #js {}})
|
||||
|
||||
(def masked-view #js {:default #js {}})
|
||||
|
||||
(def react-native-permissions #js {:default #js {}})
|
||||
|
||||
(def push-notification-ios #js {:default #js {:abandonPermissions identity}})
|
||||
@@ -308,6 +310,7 @@
|
||||
"react-native-device-info" react-native-device-info
|
||||
"react-native-push-notification" react-native-push-notification
|
||||
"react-native-linear-gradient" react-native-gradien
|
||||
"@react-native-community/masked-view" masked-view
|
||||
"react-native-blob-util" react-native-blob-util
|
||||
"react-native-navigation" react-native-navigation
|
||||
"@react-native-community/push-notification-ios" push-notification-ios
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
:small 20})
|
||||
|
||||
(defn icon-avatar
|
||||
[{:keys [size icon color]}]
|
||||
[{:keys [size icon color opacity]
|
||||
:or {opacity 20}}]
|
||||
(let [component-size (size sizes)
|
||||
circle-color (colors/custom-color color 50 20)
|
||||
circle-color (colors/custom-color color 50 opacity)
|
||||
icon-color (colors/custom-color-by-theme color 50 60)
|
||||
icon-size (case size
|
||||
:big 20
|
||||
@@ -25,4 +26,4 @@
|
||||
:align-items :center}}
|
||||
[icons/icon icon {:container-style {:width icon-size
|
||||
:height icon-size}
|
||||
:color icon-color}]]))
|
||||
:color icon-color}]]))
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.icon :as icons]
|
||||
[clojure.string :refer [upper-case split blank?]]
|
||||
[quo.theme :refer [dark?]]
|
||||
[status-im.ui.components.react :as react]))
|
||||
[quo.theme :refer [dark?]]))
|
||||
|
||||
(def sizes {:big {:outer 80
|
||||
:inner 72
|
||||
@@ -98,47 +97,46 @@
|
||||
status-indicator?
|
||||
profile-picture
|
||||
full-name]
|
||||
:or {full-name "empty name"
|
||||
status-indicator? true
|
||||
online? true
|
||||
size :big
|
||||
ring? true}}]
|
||||
(let [initials (if full-name
|
||||
(reduce str (map first (split full-name " ")))
|
||||
"")
|
||||
:or {full-name "empty name"
|
||||
status-indicator? true
|
||||
online? true
|
||||
size :big
|
||||
ring? true}}]
|
||||
(let [initials (if full-name
|
||||
(reduce str (map first (split full-name " ")))
|
||||
"")
|
||||
first-initial-letter (if full-name
|
||||
(or (first full-name) "")
|
||||
"")
|
||||
identicon? (contains? identicon-sizes size)
|
||||
small? (contains? small-sizes size)
|
||||
using-profile-picture? (-> profile-picture
|
||||
blank?
|
||||
false?)
|
||||
outer-dimensions (get-in sizes [size :outer])
|
||||
inner-dimensions (get-in sizes [size (if ring?
|
||||
:inner
|
||||
:outer)])
|
||||
font-size (get-in sizes [size :font-size])
|
||||
icon-text (if-not (or (blank? first-initial-letter)
|
||||
(blank? initials))
|
||||
(if small?
|
||||
first-initial-letter
|
||||
initials)
|
||||
"")]
|
||||
[rn/view {:style {:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
identicon? (contains? identicon-sizes size)
|
||||
small? (contains? small-sizes size)
|
||||
outer-dimensions (get-in sizes [size :outer])
|
||||
inner-dimensions (get-in sizes [size (if ring?
|
||||
:inner
|
||||
:outer)])
|
||||
font-size (get-in sizes [size :font-size])
|
||||
icon-text (if-not (or (blank? first-initial-letter)
|
||||
(blank? initials))
|
||||
(if small?
|
||||
first-initial-letter
|
||||
initials)
|
||||
"")]
|
||||
[rn/view {:style {:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
:border-radius outer-dimensions}}
|
||||
(when (and ring? identicon?)
|
||||
[icons/icon :main-icons/identicon-ring {:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
:size outer-dimensions
|
||||
[icons/icon :main-icons/identicon-ring {:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
:size outer-dimensions
|
||||
:no-color true}])
|
||||
(if using-profile-picture?
|
||||
[react/image {:style (container-styling inner-dimensions outer-dimensions)
|
||||
:source {:uri profile-picture}}]
|
||||
(if profile-picture
|
||||
;; display image
|
||||
[rn/image {:style (container-styling inner-dimensions outer-dimensions)
|
||||
:source profile-picture}]
|
||||
;; else display initials
|
||||
[container inner-dimensions outer-dimensions
|
||||
[text/text {:weight :semi-bold
|
||||
:size font-size
|
||||
:style {:color colors/white-opa-70}}
|
||||
:size font-size
|
||||
:style {:color colors/white-opa-70}}
|
||||
(upper-case icon-text)]])
|
||||
[dot-indicator size status-indicator? online? ring? (dark?)]]))
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
(ns quo2.components.buttons.dynamic-button
|
||||
(:require [quo.react-native :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[quo2.components.icon :as icon]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.markdown.text :as text]))
|
||||
|
||||
(defn- get-button-color [type pressed? customization-color]
|
||||
(if (#{:jump-to :mention} type)
|
||||
(if pressed?
|
||||
(colors/custom-color-by-theme customization-color 60 50)
|
||||
(colors/custom-color-by-theme customization-color 50 60))
|
||||
(if pressed?
|
||||
(colors/theme-colors colors/neutral-80-opa-80 colors/white-opa-80)
|
||||
(colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70))))
|
||||
|
||||
(defn- get-icon-and-text-color [type]
|
||||
(if (#{:jump-to :mention} type)
|
||||
colors/white
|
||||
(colors/theme-colors colors/white colors/neutral-100)))
|
||||
|
||||
(defn- icon-view [type]
|
||||
[icon/icon
|
||||
(case type
|
||||
:jump-to :main-icons2/jump-to
|
||||
:mention :main-icons2/mention
|
||||
:notification-down :main-icons2/arrow-down
|
||||
:notification-up :main-icons2/arrow-up
|
||||
:search-with-label :main-icons2/search
|
||||
:search :main-icons2/search
|
||||
:bottom :main-icons2/arrow-down)
|
||||
{:size 12
|
||||
:color (get-icon-and-text-color type)
|
||||
:container-style {:margin-top 6
|
||||
:margin-bottom 6
|
||||
:margin-left (case type
|
||||
:jump-to 0
|
||||
:mention 8
|
||||
:notification-down 2
|
||||
:notification-up 2
|
||||
:search-with-label 8
|
||||
:search 6
|
||||
:bottom 6)
|
||||
:margin-right (case type
|
||||
:jump-to 8
|
||||
:mention 2
|
||||
:notification-down 8
|
||||
:notification-up 8
|
||||
:search-with-label 4
|
||||
:search 6
|
||||
:bottom 6)}}])
|
||||
|
||||
(defn dynamic-button
|
||||
"[dynamic-button opts]
|
||||
opts
|
||||
{:type :jump-to/:mention/:notification-down/:notification-up/:search/:search-with-label/:bottom
|
||||
:on-press fn
|
||||
:count mentions or notifications count
|
||||
:customization-color customize jump-to and mention button color}"
|
||||
[_]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [type on-press count customization-color style]}]
|
||||
[rn/touchable-without-feedback
|
||||
{:on-press-in #(reset! pressed? true)
|
||||
:on-press-out #(reset! pressed? false)
|
||||
:on-press on-press}
|
||||
[rn/view {:style (merge
|
||||
{:flex-direction :row
|
||||
:height 24
|
||||
:border-radius 12
|
||||
:background-color (get-button-color type @pressed? (or customization-color :primary))}
|
||||
style)}
|
||||
(when (#{:mention :search :search-with-label :bottom} type)
|
||||
[icon-view type])
|
||||
(when (#{:jump-to :mention :notification-down :notification-up :search-with-label} type)
|
||||
[text/text {:weight :medium
|
||||
:size :paragraph-2
|
||||
:style {:color (get-icon-and-text-color type)
|
||||
:margin-top 2.5
|
||||
:margin-bottom 3.5
|
||||
:margin-left (case type
|
||||
:jump-to 8
|
||||
:mention 0
|
||||
:notification-down 8
|
||||
:notification-up 8
|
||||
:search-with-label 0)
|
||||
:margin-right (case type
|
||||
:jump-to 0
|
||||
:mention 8
|
||||
:notification-down 0
|
||||
:notification-up 0
|
||||
:search-with-label 8)}}
|
||||
(case type
|
||||
:jump-to (i18n/label :t/jump-to)
|
||||
:search-with-label (i18n/label :t/back)
|
||||
(:mention :notification-down :notification-up) (str count))])
|
||||
(when (#{:jump-to :notification-down :notification-up} type)
|
||||
[icon-view type])]])))
|
||||
@@ -4,7 +4,7 @@
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.tags.permission-tag :as permission]
|
||||
[quo2.components.tags.filter-tag :as filter-tag]
|
||||
[quo2.components.tags.tag :as tag]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
@@ -49,7 +49,7 @@
|
||||
(for [{:keys [id tag-label resource]} tags]
|
||||
^{:key id}
|
||||
[react/view {:margin-right 8}
|
||||
[filter-tag/filter-tag
|
||||
[tag/tag
|
||||
{:id id
|
||||
:size 24
|
||||
:label tag-label
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
(ns quo2.components.drawers.action-drawers
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.icon :as icon]
|
||||
[quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn- get-icon-color [section]
|
||||
(if (= section :bottom)
|
||||
(colors/theme-colors colors/danger-50 colors/danger-60)
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40)))
|
||||
|
||||
(defn action [section]
|
||||
(fn [{:keys [icon label sub-label right-icon on-press]}]
|
||||
[rn/touchable-opacity {:on-press on-press}
|
||||
[react/view {:style
|
||||
{:flex 1
|
||||
:height (if sub-label 56 47)
|
||||
:margin-horizontal 20
|
||||
:flex-direction :row}}
|
||||
[react/view {:style
|
||||
{:height 20
|
||||
:margin-top :auto
|
||||
:margin-bottom :auto
|
||||
:margin-right 12
|
||||
:width 20}}
|
||||
[icon/icon icon
|
||||
{:color (get-icon-color section)
|
||||
:size 20}]]
|
||||
[react/view
|
||||
{:style
|
||||
{:flex 1
|
||||
:justify-content :center}}
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
:weight :medium
|
||||
:style
|
||||
{:color (when (= section :bottom)
|
||||
(colors/theme-colors colors/danger-50 colors/danger-60))}}
|
||||
label]
|
||||
(when sub-label [text/text
|
||||
{:size :paragraph-2
|
||||
:style
|
||||
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
|
||||
sub-label])]
|
||||
(when right-icon
|
||||
[react/view {:style
|
||||
{:height 20
|
||||
:margin-top :auto
|
||||
:margin-bottom :auto
|
||||
:width 20}}
|
||||
[icon/icon right-icon
|
||||
{:color (get-icon-color section)
|
||||
:size 20}]])]]))
|
||||
|
||||
(defn action-drawer [{:keys [actions actions-with-consequence]}]
|
||||
[:<> {:style
|
||||
{:flex 1}}
|
||||
(map (action :top) actions)
|
||||
(when actions-with-consequence
|
||||
[:<>
|
||||
[rn/view {:style {:border-top-width 1
|
||||
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
|
||||
:margin-top 8
|
||||
:margin-bottom 7
|
||||
:align-items :center
|
||||
:flex-direction :row}}]
|
||||
(map (action :bottom) actions-with-consequence)])])
|
||||
@@ -1,75 +0,0 @@
|
||||
(ns quo2.components.info.nfc-prompt
|
||||
(:require [quo.react-native :as rn]
|
||||
[quo.theme :as theme]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im.ui.components.icons.icons :as icons]))
|
||||
|
||||
(defn nfc-prompt
|
||||
[{:keys [prompt-status on-press]}]
|
||||
(let [dark? (theme/dark?)
|
||||
ready? (= :ready prompt-status)
|
||||
connected? (= :connected prompt-status)
|
||||
success? (= :success prompt-status)
|
||||
ready-or-connected? (or ready? connected?)]
|
||||
[rn/view {:style {:height 400
|
||||
:flex-direction "column-reverse"}}
|
||||
[rn/view {:style {:background-color (if dark?
|
||||
colors/dark-prompt-bg
|
||||
colors/white)
|
||||
:width "100%"
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:height "100%"}}
|
||||
[rn/view {:style (cond-> {:flex-direction :row
|
||||
:justify-content :center
|
||||
:align-items :center}
|
||||
success? (assoc :display :none))}
|
||||
[rn/text {:style {:font-size 26
|
||||
:font-weight "400"
|
||||
:color (if dark?
|
||||
colors/dark-prompt-title
|
||||
colors/light-prompt-title)
|
||||
:line-height 31
|
||||
:text-align :center}}
|
||||
"Ready to Scan"]]
|
||||
[rn/view {:style {:justify-content :center
|
||||
:align-items :center
|
||||
:margin-top 29}}
|
||||
[icons/icon (if success?
|
||||
:main-icons/nfc-prompt-success
|
||||
(when ready-or-connected?
|
||||
:main-icons/nfc-prompt))
|
||||
{:width 114
|
||||
:color "nil"
|
||||
:height 114}]
|
||||
[rn/view {:style {:width 291
|
||||
:height 95
|
||||
:justify-content :center
|
||||
:align-items :center}}
|
||||
[rn/text {:style {:font-size 16
|
||||
:font-weight "400"
|
||||
:color (if dark?
|
||||
colors/white
|
||||
colors/neutral-95)
|
||||
:line-height 20}}
|
||||
(if success?
|
||||
"Success"
|
||||
(if (= :ready prompt-status)
|
||||
"Hold your iPhone near a Status Keycard"
|
||||
"Connected. Don’t move your card."))]]
|
||||
[rn/touchable-opacity {:style (cond-> {:padding-vertical 18
|
||||
:padding-horizontal 10
|
||||
:align-items :center
|
||||
:justify-content :center
|
||||
:width 291
|
||||
:border-radius 10
|
||||
:background-color (if dark?
|
||||
colors/dark-prompt-button
|
||||
colors/light-prompt-button)
|
||||
:height 52}
|
||||
success? (assoc :display :none))
|
||||
:on-press (fn [] on-press)}
|
||||
[rn/text {:style {:color (if dark?
|
||||
colors/white
|
||||
colors/neutral-95)}}
|
||||
"Cancel"]]]]]))
|
||||
@@ -0,0 +1,162 @@
|
||||
(ns quo2.components.messages.system-message
|
||||
(:require [status-im.i18n.i18n :as i18n]
|
||||
[quo.react-native :as rn]
|
||||
[status-im.utils.core :as utils]
|
||||
[quo.theme :as theme]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.reanimated :as ra]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[quo2.components.avatars.icon-avatar :as icon-avatar]))
|
||||
|
||||
(def themes-landed {:pinned colors/primary-50-opa-5
|
||||
:added colors/primary-50-opa-5
|
||||
:deleted colors/danger-50-opa-5})
|
||||
|
||||
(def themes
|
||||
{:light {:text colors/neutral-100
|
||||
:time colors/neutral-50
|
||||
:bg {:default colors/white
|
||||
:pressed colors/neutral-5
|
||||
:landed themes-landed}}
|
||||
:dark {:text colors/white
|
||||
:time colors/neutral-40
|
||||
:bg {:default colors/neutral-90
|
||||
:pressed colors/neutral-80
|
||||
:landed themes-landed}}})
|
||||
|
||||
(defn get-color [& keys]
|
||||
(reduce (fn [acc k] (get acc k (reduced acc)))
|
||||
((theme/get-theme) themes) (vec keys)))
|
||||
|
||||
(defn sm-timestamp [timestamp-str]
|
||||
[rn/view {:margin-left 6}
|
||||
[text/text {:size :label
|
||||
:style {:color (get-color :time)
|
||||
:text-transform :none}}
|
||||
timestamp-str]])
|
||||
|
||||
(defn sm-icon [{:keys [icon color opacity]}]
|
||||
[rn/view {:align-items :center
|
||||
:margin-right 8}
|
||||
[icon-avatar/icon-avatar {:size :medium
|
||||
:icon icon
|
||||
:color color
|
||||
:opacity opacity}]])
|
||||
|
||||
(defn sm-user-avatar [image]
|
||||
[rn/view {:margin-right 4}
|
||||
[user-avatar/user-avatar {:status-indicator? false
|
||||
:online? false
|
||||
:size :xxxs
|
||||
:profile-picture image
|
||||
:ring? false}]])
|
||||
|
||||
(defmulti sm-render :type)
|
||||
|
||||
(defmethod sm-render :deleted [{:keys [state action timestamp-str]}]
|
||||
[rn/view {:align-items :center
|
||||
:justify-content :space-between
|
||||
:flex 1
|
||||
:flex-direction :row}
|
||||
[rn/view {:align-items :center
|
||||
:flex-direction :row}
|
||||
[sm-icon {:icon :main-icons/delete16
|
||||
:color :danger
|
||||
:opacity (if (= state :landed) 0 5)}]
|
||||
[text/text {:size :paragraph-2
|
||||
:style {:color (get-color :text)
|
||||
:margin-right 5}}
|
||||
(i18n/label (if action :message-deleted-for-you :message-deleted))]
|
||||
(when (nil? action) [sm-timestamp timestamp-str])]
|
||||
(when action [button/button {:size 24
|
||||
:before :main-icons/timeout
|
||||
:type :grey} (i18n/label :undo)])])
|
||||
|
||||
(defmethod sm-render :added [{:keys [state mentions timestamp-str]}]
|
||||
[rn/view {:align-items :center
|
||||
:flex-direction :row}
|
||||
[sm-icon {:icon :main-icons/add-user16
|
||||
:color :primary
|
||||
:opacity (if (= state :landed) 0 5)}]
|
||||
[sm-user-avatar (:image (first mentions))]
|
||||
[text/text {:weight :semi-bold
|
||||
:size :paragraph-2}
|
||||
(:name (first mentions))]
|
||||
[text/text {:size :paragraph-2
|
||||
:style {:color (get-color :text)
|
||||
:margin-left 3
|
||||
:margin-right 3}}
|
||||
(i18n/label :added)]
|
||||
[sm-user-avatar (:image (second mentions))]
|
||||
[text/text {:weight :semi-bold
|
||||
:size :paragraph-2}
|
||||
(:name (second mentions))]
|
||||
[sm-timestamp timestamp-str]])
|
||||
|
||||
(defmethod sm-render :pinned [{:keys [state pinned-by content timestamp-str]}]
|
||||
[rn/view {:flex-direction :row
|
||||
:flex 1
|
||||
:align-items :center}
|
||||
[sm-icon {:icon :main-icons/pin16
|
||||
:color :primary
|
||||
:opacity (if (= state :landed) 0 5)}]
|
||||
[rn/view {:flex-direction :column
|
||||
:flex 1}
|
||||
[rn/view {:align-items :baseline
|
||||
:flex-direction :row}
|
||||
[text/text {:size :paragraph-2
|
||||
:weight :semi-bold
|
||||
:style {:color (get-color :text)}}
|
||||
(utils/truncate-str pinned-by 18)]
|
||||
[rn/view {:margin-left 4
|
||||
:margin-right 2}
|
||||
[text/text {:size :paragraph-2
|
||||
:style {:color (get-color :text)}}
|
||||
(i18n/label :pinned-a-message)]]
|
||||
[sm-timestamp timestamp-str]]
|
||||
[rn/view {:flex-direction :row}
|
||||
[rn/view {:flex-direction :row
|
||||
:margin-right 4}
|
||||
[sm-user-avatar (:image (:mentions content))]
|
||||
[text/text {:weight :semi-bold
|
||||
:size :label}
|
||||
(:name (:mentions content))]]
|
||||
(when (seq (:text content))
|
||||
[rn/view {:margin-right 20
|
||||
:flex-direction :row
|
||||
:flex 1}
|
||||
[text/text {:size :label
|
||||
:style {:color (get-color :text)}
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail}
|
||||
(:text content)]])
|
||||
[rn/view {:justify-content :flex-end
|
||||
:flex-direction :row
|
||||
:min-width 10}
|
||||
(when (seq (:info content))
|
||||
[text/text {:size :label
|
||||
:style {:color (get-color :time)}}
|
||||
(utils/truncate-str (:info content) 24)])]]]])
|
||||
|
||||
(defn system-message [{:keys [type] :as message}]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [sv-color (ra/use-shared-value (get-color :bg :landed type))]
|
||||
(ra/animate-shared-value-with-delay
|
||||
sv-color (get-color :bg :default type) 0 :linear 1000)
|
||||
[ra/touchable-opacity
|
||||
{:on-press #(ra/set-shared-value
|
||||
sv-color (get-color :bg :pressed type))
|
||||
:style (ra/apply-animations-to-style
|
||||
{:background-color sv-color}
|
||||
{:flex-direction :row
|
||||
:flex 1
|
||||
:border-radius 16
|
||||
:padding-vertical 9
|
||||
:padding-horizontal 11
|
||||
:width 359
|
||||
:height 52
|
||||
:background-color sv-color})}
|
||||
[sm-render message]]))])
|
||||
@@ -0,0 +1,35 @@
|
||||
(ns quo2.components.navigation.floating-shell-button
|
||||
(:require [quo.react-native :as rn]
|
||||
[quo2.components.buttons.dynamic-button :as dynamic-button]))
|
||||
|
||||
(defn dynamic-button-view [type dynamic-buttons style]
|
||||
(when-let [{:keys [count on-press customization-color]} (get dynamic-buttons type)]
|
||||
[dynamic-button/dynamic-button
|
||||
{:type type
|
||||
:on-press on-press
|
||||
:count count
|
||||
:style style
|
||||
: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]
|
||||
[rn/view {:style (merge {:flex-direction :row
|
||||
:margin-horizontal 12} 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}]]]])
|
||||
@@ -0,0 +1,175 @@
|
||||
(ns quo2.components.navigation.page-nav
|
||||
(:require [quo.react-native :as rn]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.icon :as icons]
|
||||
[status-im.utils.dimensions :as dimensions]
|
||||
[clojure.string :as string]
|
||||
[quo2.components.markdown.text :as text]))
|
||||
|
||||
(def ^:private centrify-style
|
||||
{:display :flex
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(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
|
||||
:container-style {:width (if (big? size)
|
||||
20
|
||||
16)
|
||||
:height (if (big? size)
|
||||
20
|
||||
16)}}
|
||||
(if-not (string/blank? color)
|
||||
{:color color}
|
||||
{:no-color true})))
|
||||
|
||||
(defn- mid-section-comp
|
||||
[{:keys [mid-section-description-user-icon horizontal-description? text-secondary-color align-mid? text-color mid-section-icon mid-section-main-text mid-section-type mid-section-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 mid-section-type))
|
||||
(and mid-section-description-user-icon
|
||||
(not mid-section-icon)))
|
||||
[rn/image {:source {:uri mid-section-description-user-icon}
|
||||
:style {:width 32
|
||||
:height 32
|
||||
:border-radius 32
|
||||
:margin-right 8}}])
|
||||
[rn/view {:style {:flex-direction (if horizontal-description?
|
||||
:row
|
||||
:column)}}
|
||||
[text/text {:size :paragraph-1
|
||||
:weight :semi-bold
|
||||
:style {:color text-color
|
||||
:line-height 21}}
|
||||
mid-section-main-text]
|
||||
(when mid-section-description
|
||||
[text/text {:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (cond-> {:padding-right 4
|
||||
:color text-secondary-color
|
||||
:line-height 18}
|
||||
horizontal-description? (assoc :margin-left 4 :margin-top 2))}
|
||||
mid-section-description])]])
|
||||
|
||||
(defn- mid-section
|
||||
[{:keys [horizontal-description? one-icon-align-left? mid-section-type left-align? mid-section-main-text mid-section-right-icon mid-section-main-text-icon-color mid-section-left-icon] :as mid-section-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)
|
||||
mid-section-comp-instance [mid-section-comp (assoc mid-section-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 mid-section-type
|
||||
:text-only [text/text {:size :paragraph-1
|
||||
:weight :semi-bold
|
||||
:style {:color text-color}}
|
||||
mid-section-main-text]
|
||||
:text-with-two-icons [rn/view {:style (assoc centrify-style :flex-direction :row)}
|
||||
[icons/icon mid-section-left-icon
|
||||
(icon-props mid-section-main-text-icon-color :big)]
|
||||
[text/text {:size :paragraph-1
|
||||
:weight :semi-bold
|
||||
:style {:padding-horizontal 4
|
||||
:color text-color}}
|
||||
mid-section-main-text]
|
||||
|
||||
[icons/icon mid-section-right-icon
|
||||
(icon-props mid-section-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 mid-section-left-icon
|
||||
(icon-props mid-section-main-text-icon-color :big)])
|
||||
mid-section-comp-instance]
|
||||
[rn/view {:style {:flex-direction :row
|
||||
:align-items :center}}
|
||||
mid-section-comp-instance
|
||||
(when horizontal-description?
|
||||
[icons/icon mid-section-left-icon
|
||||
(icon-props mid-section-main-text-icon-color :big)])])]
|
||||
:text-with-description mid-section-comp-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 page-nav
|
||||
[{:keys [one-icon-align-left? horizontal-description? align-mid? page-nav-color page-nav-background-uri mid-section-type mid-section-icon mid-section-main-text mid-section-left-icon mid-section-right-icon mid-section-description mid-section-description-color mid-section-description-icon mid-section-description-user-icon mid-section-main-text-icon-color left-section-icon left-section-icon-color left-section-icon-background-color right-section-icons]}]
|
||||
(let [{:keys [height width]} (dimensions/window)
|
||||
put-middle-section-on-left? (or align-mid?
|
||||
(> (count right-section-icons) 1))
|
||||
mid-section-props {:mid-section-type mid-section-type
|
||||
:horizontal-description? horizontal-description?
|
||||
:mid-section-main-text mid-section-main-text
|
||||
:one-icon-align-left? one-icon-align-left?
|
||||
:mid-section-right-icon mid-section-right-icon
|
||||
:mid-section-icon mid-section-icon
|
||||
:mid-section-main-text-icon-color mid-section-main-text-icon-color
|
||||
:mid-section-left-icon mid-section-left-icon}]
|
||||
[rn/view {:style (cond->
|
||||
{:display :flex
|
||||
:flex-direction :row
|
||||
:width width
|
||||
:height (* 0.0497 height)
|
||||
;; iPhone 11 Pro's height in Figma divided by Component height 56/1125
|
||||
:align-items :center
|
||||
:padding-horizontal 20
|
||||
: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}}
|
||||
[rn/view {:style (merge
|
||||
icon-styles
|
||||
{:background-color left-section-icon-background-color
|
||||
:width 32
|
||||
:height 32}
|
||||
(when put-middle-section-on-left? {:margin-right 5}))}
|
||||
[icons/icon left-section-icon (icon-props left-section-icon-color :big)]]
|
||||
(when put-middle-section-on-left?
|
||||
[mid-section (assoc mid-section-props
|
||||
:left-align? true
|
||||
:mid-section-description mid-section-description
|
||||
:mid-section-description-color mid-section-description-color
|
||||
:mid-section-description-icon mid-section-description-icon
|
||||
:align-mid? align-mid?
|
||||
:mid-section-description-user-icon mid-section-description-user-icon)])]
|
||||
(when-not put-middle-section-on-left?
|
||||
[mid-section mid-section-props])
|
||||
[rn/view {:style (assoc
|
||||
centrify-style
|
||||
:flex-direction :row
|
||||
:flex 1
|
||||
:justify-content :flex-end)}
|
||||
(let [last-icon-index (- (count right-section-icons) 1)]
|
||||
(map-indexed (fn [index {:keys [icon background-color icon-color]}]
|
||||
^{:key index}
|
||||
[right-section-icon {:icon icon
|
||||
:background-color background-color
|
||||
:icon-color icon-color
|
||||
:push-to-the-left? (if (= index last-icon-index) false true)}])
|
||||
right-section-icons))]]))
|
||||
@@ -1,21 +1,142 @@
|
||||
(ns quo2.components.tabs.tabs
|
||||
(:require [reagent.core :as reagent]
|
||||
(:require [oops.core :refer [oget]]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.tabs.tab :as tab]))
|
||||
[quo2.components.tabs.tab :as tab]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.utils.core :as utils]
|
||||
[status-im.utils.number :as number-utils]))
|
||||
|
||||
(def default-tab-size 32)
|
||||
|
||||
(defn tabs [{:keys [default-active on-change]}]
|
||||
(let [active-tab-id (reagent/atom default-active)]
|
||||
(fn [{:keys [data size] :or {size 32}}]
|
||||
(fn [{:keys [data size] :or {size default-tab-size}}]
|
||||
(let [active-id @active-tab-id]
|
||||
[rn/view {:flex-direction :row}
|
||||
(for [{:keys [label id]} data]
|
||||
^{:key id}
|
||||
[rn/view {:margin-right (if (= size 32) 12 8)}
|
||||
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
|
||||
[tab/tab
|
||||
{:id id
|
||||
:size size
|
||||
:active (= id active-id)
|
||||
:on-press #(do (reset! active-tab-id %)
|
||||
(when on-change
|
||||
(on-change %)))}
|
||||
label]])]))))
|
||||
{:id id
|
||||
:size size
|
||||
:active (= id active-id)
|
||||
:on-press (fn [^js press-event id]
|
||||
(reset! active-tab-id id)
|
||||
(when on-change
|
||||
(on-change press-event id)))}
|
||||
label]])]))))
|
||||
|
||||
(defn- calculate-fade-end-percentage
|
||||
[{:keys [offset-x content-width layout-width max-fade-percentage]}]
|
||||
(let [fade-percentage (max max-fade-percentage
|
||||
(/ (+ layout-width offset-x)
|
||||
content-width))]
|
||||
;; Truncate to avoid unnecessary rendering.
|
||||
(if (> fade-percentage 0.99)
|
||||
0.99
|
||||
(number-utils/naive-round fade-percentage 2))))
|
||||
|
||||
(defn scrollable-tabs
|
||||
"Just like the component `tabs`, displays horizontally scrollable tabs with
|
||||
extra options to control if/how the end of the scroll view fades.
|
||||
|
||||
Tabs are rendered using ReactNative's FlatList, which offers the convenient
|
||||
`scrollToIndex` method. FlatList accepts VirtualizedList and ScrollView props,
|
||||
and so does this component.
|
||||
|
||||
Usage:
|
||||
[tabs/scrollable-tabs
|
||||
{:scroll-on-press? true
|
||||
:fade-end? true
|
||||
:on-change #(...)
|
||||
:default-active :tab-a
|
||||
:data [{:id :tab-a :label \"Tab A\"}
|
||||
{:id :tab-b :label \"Tab B\"}]}]]
|
||||
|
||||
Opts:
|
||||
- `size` number
|
||||
- `scroll-on-press?` When non-nil, clicking on a tab centers it the middle
|
||||
(with animation enabled).
|
||||
- `fade-end?` When non-nil, causes the end of the scrollable view to fade out.
|
||||
- `fade-end-percentage` Percentage where fading starts relative to the total
|
||||
layout width of the `flat-list` data."
|
||||
[{:keys [default-active fade-end-percentage]
|
||||
:or {fade-end-percentage 0.8}}]
|
||||
(let [active-tab-id (reagent/atom default-active)
|
||||
fading (reagent/atom {:fade-end-percentage fade-end-percentage})
|
||||
flat-list-ref (atom nil)]
|
||||
(fn [{:keys [data
|
||||
fade-end-percentage
|
||||
fade-end?
|
||||
on-change
|
||||
on-scroll
|
||||
scroll-event-throttle
|
||||
scroll-on-press?
|
||||
size]
|
||||
:or {fade-end-percentage fade-end-percentage
|
||||
fade-end? false
|
||||
scroll-event-throttle 64
|
||||
scroll-on-press? false
|
||||
size default-tab-size}
|
||||
:as props}]
|
||||
(let [maybe-mask-wrapper (if fade-end?
|
||||
[react/masked-view
|
||||
{:mask-element (reagent/as-element
|
||||
[react/linear-gradient {:colors [:black :transparent]
|
||||
:locations [(get @fading :fade-end-percentage) 1]
|
||||
:start {:x 0 :y 0}
|
||||
:end {:x 1 :y 0}
|
||||
:pointer-events :none
|
||||
:style {:width "100%"
|
||||
:height "100%"}}])}]
|
||||
[:<>])]
|
||||
(conj maybe-mask-wrapper
|
||||
[rn/flat-list
|
||||
(merge (dissoc props
|
||||
:default-active
|
||||
:fade-end-percentage
|
||||
:fade-end?
|
||||
:on-change
|
||||
:scroll-on-press?
|
||||
:size)
|
||||
(when scroll-on-press?
|
||||
{:initial-scroll-index (utils/first-index #(= @active-tab-id (:id %)) data)})
|
||||
{:ref #(reset! flat-list-ref %)
|
||||
:extra-data (str @active-tab-id)
|
||||
:horizontal true
|
||||
:scroll-event-throttle scroll-event-throttle
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:data data
|
||||
:key-fn (comp str :id)
|
||||
:on-scroll (fn [^js e]
|
||||
(when fade-end?
|
||||
(let [offset-x (oget e "nativeEvent.contentOffset.x")
|
||||
content-width (oget e "nativeEvent.contentSize.width")
|
||||
layout-width (oget e "nativeEvent.layoutMeasurement.width")
|
||||
new-percentage (calculate-fade-end-percentage {:offset-x offset-x
|
||||
:content-width content-width
|
||||
:layout-width layout-width
|
||||
:max-fade-percentage fade-end-percentage})]
|
||||
;; Avoid unnecessary re-rendering.
|
||||
(when (not= new-percentage (get @fading :fade-end-percentage))
|
||||
(swap! fading assoc :fade-end-percentage new-percentage))))
|
||||
(when on-scroll
|
||||
(on-scroll e)))
|
||||
:render-fn (fn [{:keys [id label]} index]
|
||||
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)
|
||||
:padding-right (when (= index (dec (count data)))
|
||||
(get-in props [:style :padding-left]))}}
|
||||
[tab/tab {:id id
|
||||
:size size
|
||||
:active (= id @active-tab-id)
|
||||
:on-press (fn [id]
|
||||
(reset! active-tab-id id)
|
||||
(when scroll-on-press?
|
||||
(.scrollToIndex @flat-list-ref
|
||||
#js {:animated true
|
||||
:index index
|
||||
:viewPosition 0.5}))
|
||||
(when on-change
|
||||
(on-change id)))}
|
||||
label]])})])))))
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
(ns quo2.components.tags.filter-tag
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[quo.theme :as theme]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.tags.base-tag :as base-tag]))
|
||||
|
||||
(def themes {:light {:default {:border-color colors/neutral-20
|
||||
:blurred-border-color colors/neutral-80-opa-5
|
||||
:text-color {:style {:color colors/neutral-100}}}
|
||||
:active {:border-color colors/neutral-30
|
||||
:blurred-border-color colors/neutral-80-opa-10
|
||||
:text-color {:style {:color colors/neutral-100}}}
|
||||
:disabled {:border-color colors/neutral-20
|
||||
:blurred-border-color colors/neutral-80-opa-5
|
||||
:text-color {:style {:color colors/neutral-100}}}}
|
||||
:dark {:default {:border-color colors/neutral-70
|
||||
:blurred-border-color colors/white-opa-10
|
||||
:text-color {:style {:color colors/white}}}
|
||||
:active {:border-color colors/neutral-60
|
||||
:blurred-border-color colors/white-opa-20
|
||||
:text-color {:style {:color colors/white}}}
|
||||
:disabled {:border-color colors/neutral-70
|
||||
:blurred-border-color colors/white-opa-10
|
||||
:text-color {:style {:color colors/white}}}}})
|
||||
|
||||
(defn tag-resources [size type resource icon-color label text-color labelled]
|
||||
[rn/view {:style (merge {:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
(when label
|
||||
{:padding-horizontal (case size 32 12 24 8)}))}
|
||||
(when (= type :icon)
|
||||
[icons/icon resource {:container-style (when label
|
||||
{:margin-right 4})
|
||||
:resize-mode :center
|
||||
:size (case size
|
||||
32 20
|
||||
24 12)
|
||||
:color icon-color}])
|
||||
(when (= type :emoji)
|
||||
[rn/image {:source resource
|
||||
:style (merge (case size
|
||||
32 {:height 20
|
||||
:width 20}
|
||||
24 {:height 12
|
||||
:width 12})
|
||||
(when label
|
||||
{:margin-right 4}))}])
|
||||
(when labelled
|
||||
[text/text (merge {:size (case size
|
||||
32 :paragraph-1
|
||||
24 :paragraph-2
|
||||
20 :label nil)
|
||||
:weight :medium
|
||||
:number-of-lines 1}
|
||||
text-color)
|
||||
label])])
|
||||
|
||||
(defn filter-tag
|
||||
[_ _]
|
||||
(fn [{:keys [id on-press disabled size resource active accessibility-label
|
||||
label type labelled blurred icon-color] :or {size 32}}]
|
||||
(let [state (cond disabled :disabled active :active :else :default)
|
||||
{:keys [border-color blurred-border-color text-color]}
|
||||
(get-in themes [(theme/get-theme) state])]
|
||||
[base-tag/base-tag {:id id
|
||||
:size size
|
||||
:border-width 1
|
||||
:border-color (if blurred
|
||||
blurred-border-color
|
||||
border-color)
|
||||
:on-press on-press
|
||||
:accessibility-label accessibility-label
|
||||
:disabled disabled
|
||||
:type type
|
||||
:label label}
|
||||
[tag-resources size type resource icon-color label text-color labelled]])))
|
||||
|
||||
@@ -1,51 +1,80 @@
|
||||
(ns quo2.components.tags.tag
|
||||
(:require [quo.react-native :as rn]
|
||||
[quo2.foundations.colors :as colors]
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[quo.theme :as theme]
|
||||
[quo2.components.markdown.text :as text]))
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.tags.base-tag :as base-tag]))
|
||||
|
||||
(def themes {:light {:background-color colors/neutral-20}
|
||||
:dark {:background-color colors/neutral-80}})
|
||||
(def themes {:light {:default {:border-color colors/neutral-20
|
||||
:blurred-border-color colors/neutral-80-opa-5
|
||||
:text-color {:style {:color colors/neutral-100}}}
|
||||
:active {:border-color colors/neutral-30
|
||||
:blurred-border-color colors/neutral-80-opa-10
|
||||
:text-color {:style {:color colors/neutral-100}}}
|
||||
:disabled {:border-color colors/neutral-20
|
||||
:blurred-border-color colors/neutral-80-opa-5
|
||||
:text-color {:style {:color colors/neutral-100}}}}
|
||||
:dark {:default {:border-color colors/neutral-70
|
||||
:blurred-border-color colors/white-opa-10
|
||||
:text-color {:style {:color colors/white}}}
|
||||
:active {:border-color colors/neutral-60
|
||||
:blurred-border-color colors/white-opa-20
|
||||
:text-color {:style {:color colors/white}}}
|
||||
:disabled {:border-color colors/neutral-70
|
||||
:blurred-border-color colors/white-opa-10
|
||||
:text-color {:style {:color colors/white}}}}})
|
||||
|
||||
(defn get-value-from-size [size big-option small-option]
|
||||
(if (= size :big) big-option small-option))
|
||||
(defn tag-resources [size type resource icon-color label text-color labelled]
|
||||
[rn/view {:style (merge {:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
(when label
|
||||
{:padding-horizontal (case size 32 12 24 8)}))}
|
||||
(when (= type :icon)
|
||||
[icons/icon resource {:container-style (when label
|
||||
{:margin-right 4})
|
||||
:resize-mode :center
|
||||
:size (case size
|
||||
32 20
|
||||
24 12)
|
||||
:color icon-color}])
|
||||
(when (= type :emoji)
|
||||
[rn/image {:source resource
|
||||
:style (merge (case size
|
||||
32 {:height 20
|
||||
:width 20}
|
||||
24 {:height 12
|
||||
:width 12})
|
||||
(when label
|
||||
{:margin-right 4}))}])
|
||||
(when labelled
|
||||
[text/text (merge {:size (case size
|
||||
32 :paragraph-1
|
||||
24 :paragraph-2
|
||||
20 :label nil)
|
||||
:weight :medium
|
||||
:number-of-lines 1}
|
||||
text-color)
|
||||
label])])
|
||||
|
||||
(defn tag-container [size]
|
||||
{:height (get-value-from-size size 32 24)
|
||||
:align-items :center
|
||||
:flex-direction :row
|
||||
:border-radius 20})
|
||||
|
||||
(defn tag "[tag opts \"label\"]]
|
||||
opts
|
||||
{
|
||||
:size :small/:big
|
||||
:token-img-src :token-img-src
|
||||
:token-img-style {}
|
||||
:border-color :color
|
||||
:overlay child-elements
|
||||
}"
|
||||
(defn tag
|
||||
[_ _]
|
||||
(fn [{:keys [size token-img-src token-img-style border-color overlay]
|
||||
:or {size :small}} label]
|
||||
[rn/view
|
||||
{:style (when border-color
|
||||
{:border-color border-color
|
||||
:border-radius 20
|
||||
:border-width 1})}
|
||||
[rn/view
|
||||
{:style (merge (tag-container size) (get themes (theme/get-theme)))}
|
||||
[rn/image
|
||||
{:source token-img-src
|
||||
:style (merge
|
||||
{:height (get-value-from-size size 28 20)
|
||||
:width (get-value-from-size size 28 20)
|
||||
:margin-left 2
|
||||
:margin-right (get-value-from-size size 8 6)} token-img-style)}]
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:number-of-lines 1
|
||||
:style
|
||||
{:margin-right (get-value-from-size size 12 11)}
|
||||
:size (get-value-from-size size :paragraph-2 :label)} label]
|
||||
overlay]]))
|
||||
(fn [{:keys [id on-press disabled size resource active accessibility-label
|
||||
label type labelled blurred icon-color] :or {size 32}}]
|
||||
(let [state (cond disabled :disabled active :active :else :default)
|
||||
{:keys [border-color blurred-border-color text-color]}
|
||||
(get-in themes [(theme/get-theme) state])]
|
||||
[base-tag/base-tag {:id id
|
||||
:size size
|
||||
:border-width 1
|
||||
:border-color (if blurred
|
||||
blurred-border-color
|
||||
border-color)
|
||||
:on-press on-press
|
||||
:accessibility-label accessibility-label
|
||||
:disabled disabled
|
||||
:type type
|
||||
:label label}
|
||||
[tag-resources size type resource icon-color label text-color labelled]])))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns quo2.components.tags.filter-tags
|
||||
(ns quo2.components.tags.tags
|
||||
(:require [reagent.core :as reagent]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.tags.filter-tag :as tag]))
|
||||
[quo2.components.tags.tag :as tag]))
|
||||
|
||||
(defn tags [{:keys [default-active on-change]}]
|
||||
(let [active-tab-id (reagent/atom default-active)]
|
||||
@@ -11,7 +11,7 @@
|
||||
(for [{:keys [tag-label id resource]} data]
|
||||
^{:key id}
|
||||
[rn/view {:margin-right 8}
|
||||
[tag/filter-tag
|
||||
[tag/tag
|
||||
(merge {:id id
|
||||
:size size
|
||||
:type type
|
||||
@@ -2,8 +2,11 @@
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[quo.react-native :as rn]
|
||||
[quo.theme :as theme]
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[quo2.components.tags.tag :as tag]))
|
||||
[quo2.components.markdown.text :as text]
|
||||
[status-im.ui.components.icons.icons :as icons]))
|
||||
|
||||
(def themes {:light {:background-color colors/neutral-20}
|
||||
:dark {:background-color colors/neutral-80}})
|
||||
|
||||
(defn get-value-from-size [size big-option small-option]
|
||||
(if (= size :big) big-option small-option))
|
||||
@@ -16,6 +19,46 @@
|
||||
:border-radius 20
|
||||
:margin-left 2})
|
||||
|
||||
(defn tag-container [size]
|
||||
{:height (get-value-from-size size 32 24)
|
||||
:align-items :center
|
||||
:flex-direction :row
|
||||
:border-radius 20})
|
||||
|
||||
(defn tag "[tag opts \"label\"]]
|
||||
opts
|
||||
{
|
||||
:size :small/:big
|
||||
:token-img-src :token-img-src
|
||||
:token-img-style {}
|
||||
:border-color :color
|
||||
:overlay child-elements
|
||||
}"
|
||||
[_ _]
|
||||
(fn [{:keys [size token-img-src token-img-style border-color overlay]
|
||||
:or {size :small}} label]
|
||||
[rn/view
|
||||
{:style (when border-color
|
||||
{:border-color border-color
|
||||
:border-radius 20
|
||||
:border-width 1})}
|
||||
[rn/view
|
||||
{:style (merge (tag-container size) (get themes (theme/get-theme)))}
|
||||
[rn/image
|
||||
{:source token-img-src
|
||||
:style (merge
|
||||
{:height (get-value-from-size size 28 20)
|
||||
:width (get-value-from-size size 28 20)
|
||||
:margin-left 2
|
||||
:margin-right (get-value-from-size size 8 6)} token-img-style)}]
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:number-of-lines 1
|
||||
:style
|
||||
{:margin-right (get-value-from-size size 12 11)}
|
||||
:size (get-value-from-size size :paragraph-2 :label)} label]
|
||||
overlay]]))
|
||||
|
||||
(defn token-tag
|
||||
"[token-tag opts]
|
||||
opts
|
||||
@@ -33,23 +76,23 @@
|
||||
:or
|
||||
{size :small border-color (colors/custom-color-by-theme :purple 50 60)}}]
|
||||
|
||||
[tag/tag {:size size
|
||||
:token-img-src token-img-src
|
||||
:border-color (when is-required border-color)
|
||||
:overlay
|
||||
(when (or is-required is-purchasable)
|
||||
[rn/view
|
||||
{:style (merge icon-container-styles
|
||||
{:width 15.5
|
||||
:height 15.5
|
||||
:background-color border-color
|
||||
:border-color (if (= (theme/get-theme) :dark) colors/neutral-100 colors/white)
|
||||
:border-width 1
|
||||
:right (get-value-from-size size -3.75 -5.75)
|
||||
:bottom (get-value-from-size size (- 32 7.75 4) (- 24 7.75 2)) ; (- height (icon-height/2) spacing)
|
||||
})}
|
||||
[icons/icon (if is-required :main-icons2/required-checkmark12 :main-icons2/purchasable12)
|
||||
{:no-color true
|
||||
:width 13.5
|
||||
:height 13.5}]])}
|
||||
[tag {:size size
|
||||
:token-img-src token-img-src
|
||||
:border-color (when is-required border-color)
|
||||
:overlay
|
||||
(when (or is-required is-purchasable)
|
||||
[rn/view
|
||||
{:style (merge icon-container-styles
|
||||
{:width 15.5
|
||||
:height 15.5
|
||||
:background-color border-color
|
||||
:border-color (if (= (theme/get-theme) :dark) colors/neutral-100 colors/white)
|
||||
:border-width 1
|
||||
:right (get-value-from-size size -3.75 -5.75)
|
||||
:bottom (get-value-from-size size (- 32 7.75 4) (- 24 7.75 2)) ; (- height (icon-height/2) spacing)
|
||||
})}
|
||||
[icons/icon (if is-required :main-icons2/required-checkmark12 :main-icons2/purchasable12)
|
||||
{:no-color true
|
||||
:width 13.5
|
||||
:height 13.5}]])}
|
||||
(str value " " token)]))
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
|
||||
;;;; Customization
|
||||
|
||||
;; Colors for customizing profiles and communities themes
|
||||
(def customization
|
||||
{:primary {50 primary-50 ;; User can also use primary color as customisation color
|
||||
60 primary-60}
|
||||
@@ -145,6 +146,11 @@
|
||||
:beige {50 "#CAAE93"
|
||||
60 "#AA927C"}})
|
||||
|
||||
(def colors-map (merge {:danger {50 danger-50
|
||||
60 danger-60}
|
||||
:success {50 success-50
|
||||
60 success-60}} customization))
|
||||
|
||||
(def custom-color
|
||||
"(custom-color color suffix opacity)
|
||||
color :primary/:purple/...
|
||||
@@ -155,7 +161,7 @@
|
||||
([color suffix]
|
||||
(custom-color color suffix nil))
|
||||
([color suffix opacity]
|
||||
(let [base-color (get-in customization [(keyword color) suffix])]
|
||||
(let [base-color (get-in colors-map [(keyword color) suffix])]
|
||||
(if opacity (alpha base-color (/ opacity 100)) base-color))))))
|
||||
|
||||
(defn custom-color-by-theme
|
||||
@@ -188,12 +194,6 @@
|
||||
(let [theme (or override-theme (theme/get-theme))]
|
||||
(if (= theme :light) light dark))))
|
||||
|
||||
(def dark-prompt-bg "#1C1C1E")
|
||||
(def dark-prompt-title "#9F9FA5")
|
||||
(def dark-prompt-button "#8E8E93")
|
||||
(def light-prompt-title "#8F8E94")
|
||||
(def light-prompt-button "#D5D4DB")
|
||||
|
||||
(defn dark?
|
||||
[]
|
||||
(theme/dark?))
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
:type :select
|
||||
:options [{:key :main-icons/placeholder20
|
||||
:value "Placeholder"}
|
||||
{:key :main-icons/walelt
|
||||
{:key :main-icons/wallet
|
||||
:value "Wallet"}
|
||||
{:key :main-icons/play
|
||||
:value "Play"}]}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo.previews.preview :as preview]
|
||||
[quo2.components.avatars.user-avatar :as quo2]
|
||||
[reagent.core :as reagent]))
|
||||
[reagent.core :as reagent]
|
||||
[status-im.react-native.resources :as resources]))
|
||||
|
||||
(def descriptor [{:label "Size:"
|
||||
:key :size
|
||||
@@ -32,9 +33,15 @@
|
||||
{:label "Full name separated by space"
|
||||
:key :full-name
|
||||
:type :text}
|
||||
{:label "Profile Picture URL"
|
||||
:key :profile-picture
|
||||
:type :text}])
|
||||
{:label "Profile Picture"
|
||||
:key :profile-picture
|
||||
:type :select
|
||||
:options [{:value "None"
|
||||
:key nil}
|
||||
{:value "Alicia Keys"
|
||||
:key (resources/get-mock-image :user-picture-female2)}
|
||||
{:value "pedro.eth"
|
||||
:key (resources/get-mock-image :user-picture-male4)}]}])
|
||||
|
||||
(defn cool-preview []
|
||||
(let [state (reagent/atom {:full-name "A Y"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
(ns quo2.screens.buttons.dynamic-button
|
||||
(:require [quo.react-native :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[quo.previews.preview :as preview]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.buttons.dynamic-button :as quo2]))
|
||||
|
||||
(def descriptor [{:label "Type:"
|
||||
:key :type
|
||||
:type :select
|
||||
:options [{:key :jump-to
|
||||
:value "Jump To"}
|
||||
{:key :mention
|
||||
:value "Mention"}
|
||||
{:key :notification-down
|
||||
:value "Notification Down"}
|
||||
{:key :notification-up
|
||||
:value "Notification Up"}
|
||||
{:key :search
|
||||
:value "Search"}
|
||||
{:key :search-with-label
|
||||
:value "Search With Label"}
|
||||
{:key :bottom
|
||||
:value "Bottom"}]}
|
||||
{:label "Count"
|
||||
:key :count
|
||||
:type :text}])
|
||||
|
||||
(defn cool-preview []
|
||||
(let [state (reagent/atom {:count "5" :type :jump-to})]
|
||||
(fn []
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:padding-bottom 150}
|
||||
[preview/customizer state descriptor]
|
||||
[rn/view {:padding-vertical 60
|
||||
:align-items :center}
|
||||
[quo2/dynamic-button @state]]]])))
|
||||
|
||||
(defn preview-dynamic-button []
|
||||
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
|
||||
:flex 1}
|
||||
[rn/flat-list {:flex 1
|
||||
:keyboardShouldPersistTaps :always
|
||||
:header [cool-preview]
|
||||
:key-fn str}]])
|
||||
@@ -0,0 +1,59 @@
|
||||
(ns quo2.screens.drawers.action-drawers
|
||||
(:require [quo.previews.preview :as preview]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.drawers.action-drawers :as quo2]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im.utils.handlers :refer [>evt]]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def descriptor [{:label "Muted?"
|
||||
:key :muted?
|
||||
:type :boolean}
|
||||
{:label "Show red options?"
|
||||
:key :show-red-options?
|
||||
:type :boolean}])
|
||||
|
||||
(def options-with-consequences [{:icon :main-icons2/share-context
|
||||
:label "Clear history"}])
|
||||
|
||||
(defn render-action-sheet [state]
|
||||
[quo2/action-drawer {:actions-with-consequence (when (:show-red-options? @state) options-with-consequences)
|
||||
:actions [{:icon :main-icons2/share-context
|
||||
:label "View channel members and details"}
|
||||
{:icon :main-icons2/communities
|
||||
:label "Mark as read"}
|
||||
{:icon :main-icons2/muted
|
||||
:label (if (:muted? @state) "Unmute channel" "Mute channel")
|
||||
:right-icon :main-icons2/chevron-right
|
||||
:sub-label (when (:muted? @state) "Muted for 15 min")}
|
||||
{:icon :main-icons2/scanner
|
||||
:right-icon :main-icons2/chevron-right
|
||||
:label "Fetch messages"}
|
||||
{:icon :main-icons2/add-user
|
||||
:label "Share link to the channel"}]}])
|
||||
|
||||
(defn cool-preview []
|
||||
(let [state (reagent/atom {:muted? true
|
||||
:show-red-options? true})]
|
||||
(fn []
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:padding-bottom 400}
|
||||
[preview/customizer state descriptor]
|
||||
[button/button
|
||||
{:style {:margin-horizontal 40}
|
||||
:on-press #(>evt [:bottom-sheet/show-sheet
|
||||
{:content (constantly (render-action-sheet state))
|
||||
:content-height 300}])}
|
||||
"See in bottom sheet"]
|
||||
[rn/view {:padding-vertical 60}
|
||||
(render-action-sheet state)]]])))
|
||||
(defn preview-action-drawers []
|
||||
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
|
||||
:flex 1}
|
||||
|
||||
[rn/flat-list {:flex 1
|
||||
:nestedScrollEnabled true
|
||||
:keyboardShouldPersistTaps :always
|
||||
:header [cool-preview]
|
||||
:key-fn (fn [_ index] (str "actions-drawers-" index))}]])
|
||||
@@ -1,33 +0,0 @@
|
||||
(ns quo2.screens.info.nfc-prompt
|
||||
(:require [quo.react-native :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[quo.previews.preview :as preview]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.info.nfc-prompt :as quo2]))
|
||||
|
||||
(def descriptor [{:label "Prompt status"
|
||||
:key :prompt-status
|
||||
:type :select
|
||||
:options [{:key :ready
|
||||
:value "Ready"}
|
||||
{:key :connected
|
||||
:value "Connected"}
|
||||
{:key :success
|
||||
:value "Success"}]}])
|
||||
|
||||
(defn cool-preview []
|
||||
(let [state (reagent/atom {:on-press identity
|
||||
:prompt-status :ready})]
|
||||
(fn []
|
||||
[rn/view {:margin-bottom 50
|
||||
:padding-vertical 16}
|
||||
[preview/customizer state descriptor]
|
||||
[quo2/nfc-prompt @state]])))
|
||||
|
||||
(defn preview-nfc-prompt []
|
||||
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
|
||||
:flex 1}
|
||||
[rn/flat-list {:flex 1
|
||||
:keyboardShouldPersistTaps :always
|
||||
:header [cool-preview]
|
||||
:key-fn str}]])
|
||||
@@ -13,10 +13,12 @@
|
||||
[quo2.screens.avatars.user-avatar :as user-avatar]
|
||||
[quo2.screens.avatars.wallet-user-avatar :as wallet-user-avatar]
|
||||
[quo2.screens.buttons.button :as button]
|
||||
[quo2.screens.buttons.dynamic-button :as dynamic-button]
|
||||
[quo2.screens.counter.counter :as counter]
|
||||
[quo2.screens.community.community-card-view :as community-card]
|
||||
[quo2.screens.dividers.divider-label :as divider-label]
|
||||
[quo2.screens.dividers.new-messages :as new-messages]
|
||||
[quo2.screens.drawers.action-drawers :as drawers]
|
||||
[quo2.screens.dropdowns.dropdown :as dropdown]
|
||||
[quo2.screens.info.info-message :as info-message]
|
||||
[quo2.screens.info.information-box :as information-box]
|
||||
@@ -25,6 +27,7 @@
|
||||
[quo2.screens.list-items.channel :as channel]
|
||||
[quo2.screens.markdown.text :as text]
|
||||
[quo2.screens.messages.gap :as messages-gap]
|
||||
[quo2.screens.messages.system-message :as system-message]
|
||||
[quo2.screens.notifications.activity-logs :as activity-logs]
|
||||
[quo2.screens.reactions.react :as react]
|
||||
[quo2.screens.selectors.disclaimer :as disclaimer]
|
||||
@@ -32,18 +35,19 @@
|
||||
[quo2.screens.switcher.switcher-cards :as switcher-cards]
|
||||
[quo2.screens.navigation.top-nav :as top-nav]
|
||||
[quo2.screens.navigation.bottom-nav-tab :as bottom-nav-tab]
|
||||
[quo2.screens.navigation.floating-shell-button :as floating-shell-button]
|
||||
[quo2.screens.tabs.account-selector :as account-selector]
|
||||
[quo2.screens.tabs.segmented-tab :as segmented]
|
||||
[quo2.screens.tabs.tabs :as tabs]
|
||||
[quo2.screens.tags.context-tags :as context-tags]
|
||||
[quo2.screens.tags.filter-tags :as filter-tags]
|
||||
[quo2.screens.tags.tags :as tags]
|
||||
[quo2.screens.tags.permission-tag :as permission-tag]
|
||||
[quo2.screens.tags.status-tags :as status-tags]
|
||||
[quo2.screens.tags.token-tag :as token-tag]
|
||||
[quo2.screens.wallet.token-overview :as token-overview]
|
||||
[quo2.screens.wallet.network-breakdown :as network-breakdown]
|
||||
[quo2.screens.wallet.network-amount :as network-amount]
|
||||
[quo2.screens.info.nfc-prompt :as nfc-prompt]
|
||||
[quo2.screens.navigation.page-nav :as page-nav]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(def screens-categories
|
||||
@@ -64,7 +68,10 @@
|
||||
:component channel-avatar/preview-channel-avatar}]
|
||||
:buttons [{:name :button
|
||||
:insets {:top false}
|
||||
:component button/preview-button}]
|
||||
:component button/preview-button}
|
||||
{:name :dynamic-button
|
||||
:insets {:top false}
|
||||
:component dynamic-button/preview-dynamic-button}]
|
||||
:community [{:name :community-cards
|
||||
:insets {:top false}
|
||||
:component community-card/preview-community-card}]
|
||||
@@ -77,6 +84,9 @@
|
||||
{:name :new-messages
|
||||
:insets {:top false}
|
||||
:component new-messages/preview-new-messages}]
|
||||
:drawers [{:name :action-drawers
|
||||
:insets {:top false}
|
||||
:component drawers/preview-action-drawers}]
|
||||
:dropdowns [{:name :dropdown
|
||||
:insets {:top false}
|
||||
:component dropdown/preview-dropdown}]
|
||||
@@ -85,10 +95,7 @@
|
||||
:component info-message/preview-info-message}
|
||||
{:name :information-box
|
||||
:insets {:top false}
|
||||
:component information-box/preview-information-box}
|
||||
{:name :nfc-prompt
|
||||
:insets {:top false}
|
||||
:component nfc-prompt/preview-nfc-prompt}]
|
||||
:component information-box/preview-information-box}]
|
||||
:list-items [{:name :channel
|
||||
:insets {:top false}
|
||||
:component channel/preview-channel}
|
||||
@@ -100,13 +107,22 @@
|
||||
:component text/preview-text}]
|
||||
:messages [{:name :gap
|
||||
:insets {:top false}
|
||||
:component messages-gap/preview-messages-gap}]
|
||||
:component messages-gap/preview-messages-gap}
|
||||
{:name :system-messages
|
||||
:insets {:top false}
|
||||
:component system-message/preview-system-message}]
|
||||
:navigation [{:name :bottom-nav-tab
|
||||
:insets {:top false}
|
||||
:component bottom-nav-tab/preview-bottom-nav-tab}
|
||||
{:name :top-nav
|
||||
:insets {:top false}
|
||||
:component top-nav/preview-top-nav}]
|
||||
:component top-nav/preview-top-nav}
|
||||
{:name :page-nav
|
||||
:insets {:top false}
|
||||
:component page-nav/preview-page-nav}
|
||||
{:name :floating-shell-button
|
||||
:insets {:top false}
|
||||
:component floating-shell-button/preview-floating-shell-button}]
|
||||
:notifications [{:name :activity-logs
|
||||
:insets {:top false}
|
||||
:component activity-logs/preview-activity-logs}]
|
||||
@@ -134,9 +150,9 @@
|
||||
:tags [{:name :context-tags
|
||||
:insets {:top false}
|
||||
:component context-tags/preview-context-tags}
|
||||
{:name :filter-tags
|
||||
{:name :tags
|
||||
:insets {:top false}
|
||||
:component filter-tags/preview-filter-tags}
|
||||
:component tags/preview-tags}
|
||||
{:name :permission-tag
|
||||
:insets {:top false}
|
||||
:component permission-tag/preview-permission-tag}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
(ns quo2.screens.messages.system-message
|
||||
(:require [reagent.core :as reagent]
|
||||
[status-im.react-native.resources :as resources]
|
||||
[quo.react-native :as rn]
|
||||
[quo.previews.preview :as preview]
|
||||
[quo2.components.messages.system-message :as system-message]
|
||||
[quo2.foundations.colors :as colors]))
|
||||
|
||||
(def descriptor [{:label "Message Type"
|
||||
:key :type
|
||||
:type :select
|
||||
:options [{:value "Message pinned"
|
||||
:key :pinned}
|
||||
{:value "User added"
|
||||
:key :added}
|
||||
{:value "Message deleted"
|
||||
:key :deleted}]}
|
||||
{:label "Action"
|
||||
:key :action
|
||||
:type :select
|
||||
:options [{:value "none"
|
||||
:key nil}
|
||||
{:value "Undo"
|
||||
:key :undo}]}
|
||||
{:label "Pinned By"
|
||||
:key :pinned-by
|
||||
:type :text}
|
||||
{:label "Content Text"
|
||||
:key :content-text
|
||||
:type :text}
|
||||
{:label "Content Info"
|
||||
:key :content-info
|
||||
:type :text}
|
||||
{:label "Timestamp"
|
||||
:key :timestamp-str
|
||||
:type :text}])
|
||||
|
||||
(defn finalize-state [state]
|
||||
(merge @state
|
||||
{:mentions [{:name "Alicia Keys"
|
||||
:image (resources/get-mock-image :user-picture-female2)}
|
||||
{:name "pedro.eth"
|
||||
:image (resources/get-mock-image :user-picture-male4)}]
|
||||
:content {:text (:content-text @state)
|
||||
:info (:content-info @state)
|
||||
:mentions {:name "Alisher"
|
||||
:image (resources/get-mock-image :user-picture-male5)}}}))
|
||||
(defn preview []
|
||||
(let [state (reagent/atom {:type :deleted
|
||||
:pinned-by "Steve"
|
||||
:content-text "Hello! This is an example of a pinned message!"
|
||||
:content-info "3 photos"
|
||||
:timestamp-str "09:41"})]
|
||||
(fn []
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:padding-bottom 150}
|
||||
[preview/customizer state descriptor]
|
||||
[rn/view {:padding-vertical 60
|
||||
:align-items :center}
|
||||
[system-message/system-message (finalize-state state)]]]])))
|
||||
|
||||
(defn preview-system-message []
|
||||
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
|
||||
:flex 1}
|
||||
[rn/flat-list {:flex 1
|
||||
:header [preview]
|
||||
:key-fn str
|
||||
:keyboardShouldPersistTaps :always}]])
|
||||
@@ -0,0 +1,59 @@
|
||||
(ns quo2.screens.navigation.floating-shell-button
|
||||
(:require [quo.react-native :as rn]
|
||||
[quo.previews.preview :as preview]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.components.navigation.floating-shell-button :as quo2]
|
||||
[quo2.foundations.colors :as colors]))
|
||||
|
||||
(def descriptor [{:label "Show jump to?"
|
||||
:key :show-jump-to?
|
||||
:type :boolean}
|
||||
{:label "Show search?"
|
||||
:key :show-search?
|
||||
:type :boolean}
|
||||
{:label "Show mention?"
|
||||
:key :show-mention?
|
||||
:type :boolean}
|
||||
{:label "Scroll Type"
|
||||
:key :scroll-type
|
||||
:type :select
|
||||
:options [{:key :notification-up
|
||||
:value "Notification Up"}
|
||||
{:key :notification-down
|
||||
:value "Notification Down"}
|
||||
{:key :scroll-to-bottom
|
||||
:value "Scroll To Bottom"}]}])
|
||||
|
||||
(defn mock-data [{:keys [show-jump-to? show-search? show-mention? scroll-type]}]
|
||||
(cond-> {}
|
||||
show-jump-to?
|
||||
(assoc :jump-to {:on-press #()})
|
||||
show-search?
|
||||
(assoc :search {:on-press #()})
|
||||
show-mention?
|
||||
(assoc :mention {:on-press #() :count 6 :customization-color :turquoise})
|
||||
(= scroll-type :notification-up)
|
||||
(assoc :notification-up {:on-press #() :count 8})
|
||||
(= scroll-type :notification-down)
|
||||
(assoc :notification-down {:on-press #() :count 8})
|
||||
(= scroll-type :scroll-to-bottom)
|
||||
(assoc :bottom {:on-press #()})))
|
||||
|
||||
(defn cool-preview []
|
||||
(let [state (reagent/atom {:show-jump-to? true
|
||||
:scroll-type :notification-down})]
|
||||
(fn []
|
||||
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
|
||||
[rn/view {:padding-bottom 150}
|
||||
[preview/customizer state descriptor]
|
||||
[rn/view {:padding-vertical 60
|
||||
:align-items :center}
|
||||
[quo2/floating-shell-button (mock-data @state)]]]])))
|
||||
|
||||
(defn preview-floating-shell-button []
|
||||
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
|
||||
:flex 1}
|
||||
[rn/flat-list {:flex 1
|
||||
:keyboardShouldPersistTaps :always
|
||||
:header [cool-preview]
|
||||
:key-fn str}]])
|
||||
@@ -0,0 +1,106 @@
|
||||
(ns quo2.screens.navigation.page-nav
|
||||
(:require [quo.previews.preview :as preview]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.navigation.page-nav :as quo2]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def ^:private descriptor
|
||||
[{:label "Page nav variation"
|
||||
:key :selected-variation
|
||||
:type :select
|
||||
:options [{:key :text-only?
|
||||
:value "Text only"}
|
||||
{:key :align-left?
|
||||
:value "Align Left"}
|
||||
{:key :align-left-top-down-text?
|
||||
:value "Align left top down text?"}
|
||||
{:key :align-left-with-icon?
|
||||
:value "Align Left with icon ?"}
|
||||
{:key :one-icon-align-left?
|
||||
:value "One icon on the left ?"}
|
||||
{:key :one-icon-align-right?
|
||||
:value "One icon on the right ?"}
|
||||
{:key :two-icons?
|
||||
:value "Two icons ?"}
|
||||
{:key :user-icon?
|
||||
:value "User icon ?"}
|
||||
{:key :empty?
|
||||
:value "Empty ?"}]}
|
||||
{:label "Number of right icons"
|
||||
:key :number-of-right-icons
|
||||
:type :select
|
||||
:options [{:key 1
|
||||
:value 1}
|
||||
{:key 2
|
||||
:value 2}
|
||||
{:key 3
|
||||
:value 3}]}])
|
||||
|
||||
(def ^:private selected-variation (reagent/atom {:selected-variation :text-only?
|
||||
:number-of-right-icons 1}))
|
||||
|
||||
(defn- cool-preview []
|
||||
(let [right-icon {:background-color (if (colors/dark?)
|
||||
colors/neutral-80
|
||||
colors/neutral-20)
|
||||
:icon :main-icons/placeholder
|
||||
:icon-color nil}
|
||||
base-props {:horizontal-description? true
|
||||
:one-icon-align-left? true
|
||||
:align-mid? false
|
||||
:page-nav-color :transparent
|
||||
:page-nav-background-uri ""
|
||||
:mid-section-type :text-with-description
|
||||
:mid-section-icon :main-icons/placeholder
|
||||
:mid-section-main-text "Status"
|
||||
:mid-section-left-icon :main-icons/placeholder
|
||||
:mid-section-right-icon :main-icons/placeholder
|
||||
:mid-section-description "SNT"
|
||||
:mid-section-description-color "black"
|
||||
:mid-section-description-icon :main-icons/placeholder
|
||||
:mid-section-description-user-icon "https://i.picsum.photos/id/810/200/300.jpg?hmac=HgwlXd-OaLOAqhGyCiZDUb_75EgUI4u0GtS7nfgxd8s"
|
||||
:left-section-icon :main-icons/unlocked
|
||||
:left-section-icon-background-color (if (colors/dark?)
|
||||
colors/neutral-80
|
||||
colors/neutral-20)}
|
||||
create-variation #(merge %1 %2)
|
||||
variations {:text-only? base-props
|
||||
:align-left? (create-variation base-props {:align-mid? true})
|
||||
:one-icon-align-left? (create-variation base-props {:one-icon-align-left? true
|
||||
:mid-section-type :text-with-one-icon})
|
||||
:one-icon-align-right? (create-variation base-props {:one-icon-align-left? false
|
||||
:mid-section-type :text-with-one-icon})
|
||||
:two-icons? (create-variation base-props {:mid-section-type :text-with-two-icons})
|
||||
:user-icon? (create-variation base-props {:align-mid? true
|
||||
:horizontal-description? false
|
||||
:mid-section-type :text-with-one-icon})
|
||||
:empty? (create-variation base-props {:mid-section-main-text ""
|
||||
:mid-section-description ""})
|
||||
:align-left-with-icon? (create-variation base-props {:align-mid? true
|
||||
:mid-section-type :text-with-one-icon})
|
||||
:align-left-top-down-text? (create-variation base-props {:align-mid? true
|
||||
:horizontal-description? false
|
||||
:mid-section-type :text-with-description})}
|
||||
state (reagent/atom (-> (get variations (:selected-variation @selected-variation))
|
||||
(assoc :right-section-icons (repeat (:number-of-right-icons @selected-variation) right-icon))))]
|
||||
(fn []
|
||||
[rn/view {:margin-bottom 50
|
||||
:padding 16}
|
||||
[rn/view {:flex 1}
|
||||
[preview/customizer selected-variation descriptor]]
|
||||
[rn/view {:padding-vertical 30
|
||||
:flex-direction :row
|
||||
:justify-content :center}
|
||||
[quo2/page-nav @state]]])))
|
||||
|
||||
(def ^:private trackable-cool-preview (reagent/track cool-preview selected-variation))
|
||||
|
||||
(defn preview-page-nav []
|
||||
[rn/view {:background-color (colors/theme-colors colors/white
|
||||
colors/neutral-90)
|
||||
:flex 1}
|
||||
[rn/flat-list {:flex 1
|
||||
:keyboardShouldPersistTaps :always
|
||||
:header [@trackable-cool-preview]
|
||||
:key-fn str}]])
|
||||
@@ -14,8 +14,7 @@
|
||||
[rn/view {:padding-vertical 60
|
||||
:align-items :center}
|
||||
[quo2/disclaimer
|
||||
{:checked? @checked?
|
||||
:container-style {:margin-bottom 40}
|
||||
{:container-style {:margin-bottom 40}
|
||||
:on-change #(swap! checked? not)}
|
||||
"I agree with the community rules"]
|
||||
[button/button
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
[rn/view {:flex 1}
|
||||
[preview/customizer state descriptor]]
|
||||
[rn/view {:padding-vertical 60
|
||||
:align-self :center
|
||||
:justify-content :center}
|
||||
(when @state
|
||||
(for [{:keys [tokens]} community-tokens]
|
||||
@@ -160,7 +161,7 @@
|
||||
(defn preview-permission-tag []
|
||||
[rn/view {:background-color (colors/theme-colors
|
||||
colors/white
|
||||
colors/neutral-80)
|
||||
colors/neutral-90)
|
||||
:flex 1}
|
||||
[rn/flat-list {:flex 1
|
||||
:keyboardShouldPersistTaps :always
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(ns quo2.screens.tags.filter-tags
|
||||
(ns quo2.screens.tags.tags
|
||||
(:require [quo.react-native :as rn]
|
||||
[quo.previews.preview :as preview]
|
||||
[status-im.ui.components.react :as react]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.tags.filter-tags :as quo2.tags]
|
||||
[quo2.components.tags.tags :as tags]
|
||||
[status-im.react-native.resources :as resources]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
@@ -70,15 +70,15 @@
|
||||
:align-items :center
|
||||
:position :absolute
|
||||
:padding-horizontal 10}
|
||||
[quo2.tags/tags (merge @state
|
||||
{:default-active 1
|
||||
:data [{:id 1 :tag-label "Music" :resource (resources/get-image :music)}
|
||||
{:id 2 :tag-label "Lifestyle" :resource (resources/get-image :lifestyle)}
|
||||
{:id 3 :tag-label "Podcasts" :resource (resources/get-image :podcasts)}]})]]]]])))
|
||||
(defn preview-filter-tags []
|
||||
[tags/tags (merge @state
|
||||
{:default-active 1
|
||||
:data [{:id 1 :tag-label "Music" :resource (resources/get-image :music)}
|
||||
{:id 2 :tag-label "Lifestyle" :resource (resources/get-image :lifestyle)}
|
||||
{:id 3 :tag-label "Podcasts" :resource (resources/get-image :podcasts)}]})]]]]])))
|
||||
(defn preview-tags []
|
||||
[rn/view {:flex 1
|
||||
:background-color (colors/theme-colors
|
||||
colors/neutral-5
|
||||
colors/white
|
||||
colors/neutral-90)}
|
||||
[rn/flat-list {:flex 1
|
||||
:keyboardShouldPersistTaps :always
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns status-im.activity-center.core
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.activities :as data-store.activities]
|
||||
[status-im.ethereum.json-rpc :as json-rpc]
|
||||
[status-im.utils.fx :as fx]
|
||||
@@ -8,90 +9,117 @@
|
||||
;;;; Notification reconciliation
|
||||
|
||||
(defn- update-notifications
|
||||
[old new]
|
||||
(let [ids-to-be-removed (->> new
|
||||
(filter #(or (:dismissed %) (:accepted %)))
|
||||
(map :id))
|
||||
grouped-new (apply dissoc (group-by :id new) ids-to-be-removed)
|
||||
grouped-old (apply dissoc (group-by :id old) ids-to-be-removed)]
|
||||
(->> (merge grouped-old grouped-new)
|
||||
vals
|
||||
(map first))))
|
||||
"Insert `new-notifications` in `db-notifications`.
|
||||
|
||||
Although correct, this is a naive implementation for reconciling notifications
|
||||
because for every notification in `new-notifications`, linear scans will be
|
||||
performed to remove it and sorting will be performed for every new insertion.
|
||||
If the number of existing notifications cached in the app db becomes
|
||||
~excessively~ big, this implementation will probably need to be revisited."
|
||||
[db-notifications new-notifications]
|
||||
(reduce (fn [acc {:keys [id type read] :as notification}]
|
||||
(let [filter-status (if read :read :unread)]
|
||||
(cond-> (-> acc
|
||||
(update-in [type :read :data]
|
||||
(fn [data]
|
||||
(remove #(= id (:id %)) data)))
|
||||
(update-in [type :unread :data]
|
||||
(fn [data]
|
||||
(remove #(= id (:id %)) data))))
|
||||
(not (or (:dismissed notification) (:accepted notification)))
|
||||
(update-in [type filter-status :data]
|
||||
(fn [data]
|
||||
(->> notification
|
||||
(conj data)
|
||||
(sort-by (juxt :timestamp :id))
|
||||
reverse))))))
|
||||
db-notifications
|
||||
new-notifications))
|
||||
|
||||
(fx/defn notifications-reconcile
|
||||
{:events [:activity-center.notifications/reconcile]}
|
||||
[{:keys [db]} new-notifications]
|
||||
(let [{read-new true
|
||||
unread-new false} (group-by :read new-notifications)
|
||||
read-old (get-in db [:activity-center :notifications-read :data])
|
||||
unread-old (get-in db [:activity-center :notifications-unread :data])]
|
||||
{:db (-> db
|
||||
(assoc-in [:activity-center :notifications-read :data]
|
||||
(update-notifications read-old read-new))
|
||||
(assoc-in [:activity-center :notifications-unread :data]
|
||||
(update-notifications unread-old unread-new)))}))
|
||||
(when (seq new-notifications)
|
||||
{:db (update-in db [:activity-center :notifications]
|
||||
update-notifications new-notifications)}))
|
||||
|
||||
;;;; Notifications fetching and pagination
|
||||
|
||||
(def notifications-per-page
|
||||
20)
|
||||
(def defaults
|
||||
{:filter-status :unread
|
||||
:filter-type constants/activity-center-notification-type-no-type
|
||||
:notifications-per-page 10})
|
||||
|
||||
(def start-or-end-cursor
|
||||
"")
|
||||
|
||||
(defn notifications-group->rpc-method
|
||||
[notifications-group]
|
||||
(if (= notifications-group :notifications-read)
|
||||
(defn- valid-cursor?
|
||||
[cursor]
|
||||
(and (some? cursor)
|
||||
(not= cursor start-or-end-cursor)))
|
||||
|
||||
(defn- filter-status->rpc-method
|
||||
[filter-status]
|
||||
(if (= filter-status :read)
|
||||
"wakuext_readActivityCenterNotifications"
|
||||
"wakuext_unreadActivityCenterNotifications"))
|
||||
|
||||
(defn notifications-read-status->group
|
||||
[status-filter]
|
||||
(if (= status-filter :read)
|
||||
:notifications-read
|
||||
:notifications-unread))
|
||||
|
||||
(fx/defn notifications-fetch
|
||||
[{:keys [db]} cursor notifications-group]
|
||||
(when-not (get-in db [:activity-center notifications-group :loading?])
|
||||
{:db (assoc-in db [:activity-center notifications-group :loading?] true)
|
||||
::json-rpc/call [{:method (notifications-group->rpc-method notifications-group)
|
||||
:params [cursor notifications-per-page]
|
||||
:on-success #(re-frame/dispatch [:activity-center.notifications/fetch-success notifications-group %])
|
||||
:on-error #(re-frame/dispatch [:activity-center.notifications/fetch-error notifications-group %])}]}))
|
||||
[{:keys [db]} {:keys [cursor filter-type filter-status reset-data?]}]
|
||||
(when-not (get-in db [:activity-center :notifications filter-type filter-status :loading?])
|
||||
{:db (assoc-in db [:activity-center :notifications filter-type filter-status :loading?] true)
|
||||
::json-rpc/call [{:method (filter-status->rpc-method filter-status)
|
||||
:params [cursor (defaults :notifications-per-page) filter-type]
|
||||
:on-success #(re-frame/dispatch [:activity-center.notifications/fetch-success filter-type filter-status reset-data? %])
|
||||
:on-error #(re-frame/dispatch [:activity-center.notifications/fetch-error filter-type filter-status %])}]}))
|
||||
|
||||
(fx/defn notifications-fetch-first-page
|
||||
{:events [:activity-center.notifications/fetch-first-page]}
|
||||
[{:keys [db] :as cofx} {:keys [status-filter] :or {status-filter :unread}}]
|
||||
(let [notifications-group (notifications-read-status->group status-filter)]
|
||||
[{:keys [db] :as cofx} {:keys [filter-type filter-status]}]
|
||||
(let [filter-type (or filter-type
|
||||
(get-in db [:activity-center :filter :type]
|
||||
(defaults :filter-type)))
|
||||
filter-status (or filter-status
|
||||
(get-in db [:activity-center :filter :status]
|
||||
(defaults :filter-status)))]
|
||||
(fx/merge cofx
|
||||
{:db (-> db
|
||||
(assoc-in [:activity-center :current-status-filter] status-filter)
|
||||
(update-in [:activity-center notifications-group] dissoc :loading?)
|
||||
(update-in [:activity-center notifications-group] dissoc :data))}
|
||||
(notifications-fetch start-or-end-cursor notifications-group))))
|
||||
(assoc-in [:activity-center :filter :type] filter-type)
|
||||
(assoc-in [:activity-center :filter :status] filter-status))}
|
||||
(notifications-fetch {:cursor start-or-end-cursor
|
||||
:filter-type filter-type
|
||||
:filter-status filter-status
|
||||
:reset-data? true}))))
|
||||
|
||||
(fx/defn notifications-fetch-next-page
|
||||
{:events [:activity-center.notifications/fetch-next-page]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [status-filter (get-in db [:activity-center :current-status-filter])
|
||||
notifications-group (notifications-read-status->group status-filter)
|
||||
{:keys [cursor]} (get-in db [:activity-center notifications-group])]
|
||||
(when-not (= cursor start-or-end-cursor)
|
||||
(notifications-fetch cofx cursor notifications-group))))
|
||||
(let [{:keys [type status]} (get-in db [:activity-center :filter])
|
||||
{:keys [cursor]} (get-in db [:activity-center :notifications type status])]
|
||||
(when (valid-cursor? cursor)
|
||||
(notifications-fetch cofx {:cursor cursor
|
||||
:filter-type type
|
||||
:filter-status status
|
||||
:reset-data? false}))))
|
||||
|
||||
(fx/defn notifications-fetch-success
|
||||
{:events [:activity-center.notifications/fetch-success]}
|
||||
[{:keys [db]} notifications-group {:keys [cursor notifications]}]
|
||||
{:db (-> db
|
||||
(update-in [:activity-center notifications-group] dissoc :loading?)
|
||||
(assoc-in [:activity-center notifications-group :cursor] cursor)
|
||||
(update-in [:activity-center notifications-group :data]
|
||||
concat
|
||||
(map data-store.activities/<-rpc notifications)))})
|
||||
[{:keys [db]}
|
||||
filter-type
|
||||
filter-status
|
||||
reset-data?
|
||||
{:keys [cursor notifications]}]
|
||||
(let [processed (map data-store.activities/<-rpc notifications)]
|
||||
{:db (-> db
|
||||
(assoc-in [:activity-center :notifications filter-type filter-status :cursor] cursor)
|
||||
(update-in [:activity-center :notifications filter-type filter-status] dissoc :loading?)
|
||||
(update-in [:activity-center :notifications filter-type filter-status :data]
|
||||
(if reset-data?
|
||||
(constantly processed)
|
||||
#(concat %1 processed))))}))
|
||||
|
||||
(fx/defn notifications-fetch-error
|
||||
{:events [:activity-center.notifications/fetch-error]}
|
||||
[{:keys [db]} notifications-group error]
|
||||
[{:keys [db]} filter-type filter-status error]
|
||||
(log/warn "Failed to load Activity Center notifications" error)
|
||||
{:db (update-in db [:activity-center notifications-group] dissoc :loading?)})
|
||||
{:db (update-in db [:activity-center :notifications filter-type filter-status] dissoc :loading?)})
|
||||
|
||||
@@ -2,99 +2,285 @@
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[day8.re-frame.test :as rf-test]
|
||||
[re-frame.core :as rf]
|
||||
[status-im.constants :as c]
|
||||
[status-im.ethereum.json-rpc :as json-rpc]
|
||||
[status-im.test-helpers :as h]
|
||||
status-im.events))
|
||||
status-im.events
|
||||
[status-im.test-helpers :as h]))
|
||||
|
||||
(defn setup []
|
||||
(h/register-helper-events)
|
||||
(rf/dispatch [:init/app-started]))
|
||||
|
||||
(defn remove-color-key
|
||||
"Remove `:color` key from notifications because they have random values that we
|
||||
can't assert against."
|
||||
[grouped-notifications {:keys [type status]}]
|
||||
(update-in grouped-notifications
|
||||
[type status :data]
|
||||
(fn [old _]
|
||||
(map #(dissoc % :color) old))
|
||||
nil))
|
||||
|
||||
(deftest notifications-reconcile-test
|
||||
(testing "does nothing when there are no new notifications"
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(let [read [{:id "0x1" :read true}]
|
||||
unread [{:id "0x4" :read false}]]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data] read])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data] unread])
|
||||
(let [notifications {c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat}
|
||||
{:id "0x2"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x3"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]}}
|
||||
c/activity-center-notification-type-private-group-chat
|
||||
{:unread {:cursor ""
|
||||
:data [{:id "0x4"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat}]}}}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile nil])
|
||||
|
||||
(is (= read (get-in (h/db) [:activity-center :notifications-read :data])))
|
||||
(is (= unread (get-in (h/db) [:activity-center :notifications-unread :data]))))))
|
||||
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "removes dismissed or accepted notifications"
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data]
|
||||
[{:id "0x1" :read true}
|
||||
{:id "0x2" :read true}
|
||||
{:id "0x3" :read true}]])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data]
|
||||
[{:id "0x4" :read false}
|
||||
{:id "0x5" :read false}
|
||||
{:id "0x6" :read false}]])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat}
|
||||
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat}]}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat}]}}
|
||||
2 {:unread {:cursor ""
|
||||
:data [{:id "0x4" :read false :type 2}
|
||||
{:id "0x6" :read false :type 2}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile
|
||||
[{:id "0x1" :read true :dismissed true}
|
||||
{:id "0x3" :read true :accepted true}
|
||||
{:id "0x4" :read false :dismissed true}
|
||||
{:id "0x5" :read false :accepted true}]])
|
||||
[{:id "0x1"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:dismissed true}
|
||||
{:id "0x3"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:accepted true}
|
||||
{:id "0x4"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat
|
||||
:dismissed true}
|
||||
{:id "0x5"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat
|
||||
:accepted true}]])
|
||||
|
||||
(is (= [{:id "0x2" :read true}]
|
||||
(get-in (h/db) [:activity-center :notifications-read :data])))
|
||||
(is (= [{:id "0x6" :read false}]
|
||||
(get-in (h/db) [:activity-center :notifications-unread :data])))))
|
||||
(is (= {c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x2"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]}
|
||||
:unread {:cursor ""
|
||||
:data []}}
|
||||
c/activity-center-notification-type-private-group-chat
|
||||
{:read {:data []}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x6"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat}]}}}
|
||||
(get-in (h/db) [:activity-center :notifications])))))
|
||||
|
||||
(testing "replaces old notifications with newly arrived ones"
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data]
|
||||
[{:id "0x1" :read true}
|
||||
{:id "0x2" :read true}]])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data]
|
||||
[{:id "0x3" :read false}
|
||||
{:id "0x4" :read false}]])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]}}
|
||||
c/activity-center-notification-type-private-group-chat
|
||||
{:unread {:cursor ""
|
||||
:data [{:id "0x4"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat}
|
||||
{:id "0x6"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile
|
||||
[{:id "0x1" :read true :name "ABC"}
|
||||
{:id "0x3" :read false :name "XYZ"}]])
|
||||
[{:id "0x1"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:last-message {}}
|
||||
{:id "0x4"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat
|
||||
:author "0xabc"}
|
||||
{:id "0x6"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat}]])
|
||||
|
||||
(is (= [{:id "0x1" :read true :name "ABC"}
|
||||
{:id "0x2" :read true}]
|
||||
(get-in (h/db) [:activity-center :notifications-read :data])))
|
||||
(is (= [{:id "0x3" :read false :name "XYZ"}
|
||||
{:id "0x4" :read false}]
|
||||
(get-in (h/db) [:activity-center :notifications-unread :data]))))))
|
||||
(is (= {c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:last-message {}}]}
|
||||
:unread {:data []}}
|
||||
c/activity-center-notification-type-private-group-chat
|
||||
{:read {:data []}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x6"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat}
|
||||
{:id "0x4"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-private-group-chat
|
||||
:author "0xabc"}]}}}
|
||||
(get-in (h/db) [:activity-center :notifications])))))
|
||||
|
||||
(testing "reconciles notifications that switched their read/unread status"
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile
|
||||
[{:id "0x1"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]])
|
||||
|
||||
(is (= {c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data []}
|
||||
:unread {:data [{:id "0x1"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]}}}
|
||||
(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)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}
|
||||
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}]}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 50}
|
||||
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}
|
||||
{:id "0x5" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile
|
||||
[{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1 :last-message {}}
|
||||
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100 :last-message {}}]])
|
||||
|
||||
(is (= {c/activity-center-notification-type-one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x2"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:timestamp 1}
|
||||
{:id "0x1"
|
||||
:read true
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:timestamp 1
|
||||
:last-message {}}]}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x5"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:timestamp 100}
|
||||
{:id "0x4"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:timestamp 100
|
||||
:last-message {}}
|
||||
{:id "0x3"
|
||||
:read false
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:timestamp 50}]}}}
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(deftest notifications-fetch-test
|
||||
(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 [{:chatId "0x1"}]}))
|
||||
(h/stub-fx-with-callbacks
|
||||
::json-rpc/call
|
||||
:on-success (constantly {:cursor "10"
|
||||
:notifications [{:id "0x1"
|
||||
:type c/activity-center-notification-type-one-to-one-chat
|
||||
:read false
|
||||
:chatId "0x9"}]}))
|
||||
(h/spy-fx spy-queue ::json-rpc/call)
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-first-page])
|
||||
(rf/dispatch [:activity-center.notifications/fetch-first-page
|
||||
{:filter-type c/activity-center-notification-type-one-to-one-chat}])
|
||||
|
||||
(is (= :unread (get-in (h/db) [:activity-center :current-status-filter])))
|
||||
(is (nil? (get-in (h/db) [:activity-center :notifications-unread :loading?])))
|
||||
(is (= "10" (get-in (h/db) [:activity-center :notifications-unread :cursor])))
|
||||
(is (= "" (get-in @spy-queue [0 :args 0 :params 0])))
|
||||
(is (= [{:chat-id "0x1"}]
|
||||
(->> (get-in (h/db) [:activity-center :notifications-unread :data])
|
||||
(map #(select-keys % [:chat-id]))))))))
|
||||
(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 (= {c/activity-center-notification-type-one-to-one-chat
|
||||
{:unread {:cursor "10"
|
||||
:data [{:chat-id "0x9"
|
||||
:chat-name nil
|
||||
:chat-type c/activity-center-notification-type-one-to-one-chat
|
||||
:group-chat false
|
||||
:id "0x1"
|
||||
:public? false
|
||||
:last-message nil
|
||||
:message nil
|
||||
:read false
|
||||
:reply-message nil
|
||||
:type c/activity-center-notification-type-one-to-one-chat}]}}}
|
||||
(remove-color-key (get-in (h/db) [:activity-center :notifications])
|
||||
{:status :unread
|
||||
:type c/activity-center-notification-type-one-to-one-chat}))))))
|
||||
|
||||
(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 :current-status-filter] :unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
c/activity-center-notification-type-one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-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]
|
||||
c/activity-center-notification-type-one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
|
||||
nil])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
|
||||
@@ -104,30 +290,54 @@
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/stub-fx-with-callbacks ::json-rpc/call
|
||||
:on-success (constantly {:cursor ""
|
||||
:notifications [{:chatId "0x1"}]}))
|
||||
(h/stub-fx-with-callbacks
|
||||
::json-rpc/call
|
||||
:on-success (constantly {:cursor ""
|
||||
:notifications [{:id "0x1"
|
||||
:type c/activity-center-notification-type-mention
|
||||
:read false
|
||||
:chatId "0x9"}]}))
|
||||
(h/spy-fx spy-queue ::json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] "10"])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
c/activity-center-notification-type-mention])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-mention :unread :cursor]
|
||||
"10"])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
|
||||
(is (= "wakuext_unreadActivityCenterNotifications" (get-in @spy-queue [0 :args 0 :method])))
|
||||
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0])))
|
||||
(is (= "" (get-in (h/db) [:activity-center :notifications-unread :cursor])))
|
||||
(is (= [{:chat-id "0x1"}]
|
||||
(->> (get-in (h/db) [:activity-center :notifications-unread :data])
|
||||
(map #(select-keys % [:chat-id]))))))))
|
||||
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0]))
|
||||
"Should be called with current cursor")
|
||||
(is (= {c/activity-center-notification-type-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 c/activity-center-notification-type-mention}]}}}
|
||||
(remove-color-key (get-in (h/db) [:activity-center :notifications])
|
||||
{:status :unread
|
||||
:type c/activity-center-notification-type-mention}))))))
|
||||
|
||||
(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 :current-status-filter] :read])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :cursor] "10"])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :loading?] true])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:read])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
c/activity-center-notification-type-one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :cursor]
|
||||
"10"])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :loading?]
|
||||
true])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
|
||||
@@ -140,13 +350,18 @@
|
||||
(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 :current-status-filter] :unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
c/activity-center-notification-type-one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
|
||||
""])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-first-page])
|
||||
|
||||
(is (nil? (get-in (h/db) [:activity-center :notifications-unread :loading?])))
|
||||
(is (nil? (get-in (h/db) [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :loading?])))
|
||||
(is (= [:activity-center.notifications/fetch-error
|
||||
:notifications-unread
|
||||
c/activity-center-notification-type-one-to-one-chat
|
||||
:unread
|
||||
:fake-error]
|
||||
(:args (last @spy-queue))))))))
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
[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]))
|
||||
[status-im.utils.debounce :as debounce]
|
||||
[status-im.browser.eip3085 :as eip3085]
|
||||
[status-im.browser.eip3326 :as eip3326]))
|
||||
|
||||
(fx/defn update-browser-option
|
||||
[{:keys [db]} option-key option-value]
|
||||
@@ -345,7 +347,7 @@
|
||||
constants/web3-eth-sign constants/web3-keycard-sign-typed-data} method))
|
||||
|
||||
(fx/defn web3-send-async
|
||||
[cofx {:keys [method params id] :as payload} message-id]
|
||||
[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])
|
||||
typed? (and (not= constants/web3-personal-sign method) (not= constants/web3-eth-sign method))]
|
||||
@@ -371,25 +373,33 @@
|
||||
(dissoc :gasPrice))})
|
||||
{:on-result [:browser.dapp/transaction-on-result message-id id]
|
||||
:on-error [:browser.dapp/transaction-on-error message-id]}))))
|
||||
(if (#{"eth_accounts" "eth_coinbase"} method)
|
||||
(cond
|
||||
(#{"eth_accounts" "eth_coinbase"} method)
|
||||
(send-to-bridge cofx {:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:result {:jsonrpc "2.0"
|
||||
:id (int id)
|
||||
:result (if (= method "eth_coinbase") dapps-address [dapps-address])}})
|
||||
(if (= method "personal_ecRecover")
|
||||
{:signing.fx/recover-message {:params {:message (first params)
|
||||
:signature (second params)}
|
||||
:on-completed #(re-frame/dispatch [:browser.callback/call-rpc
|
||||
{:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:result (types/json->clj %)}])}}
|
||||
{:browser/call-rpc [payload
|
||||
#(re-frame/dispatch [:browser.callback/call-rpc
|
||||
{:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:error %1
|
||||
:result %2}])]})))))
|
||||
(= method "personal_ecRecover")
|
||||
{:signing.fx/recover-message {:params {:message (first params)
|
||||
:signature (second params)}
|
||||
:on-completed #(re-frame/dispatch [:browser.callback/call-rpc
|
||||
{:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:result (types/json->clj %)}])}}
|
||||
(= method "wallet_switchEthereumChain")
|
||||
(eip3326/handle-switch-ethereum-chain cofx dapp-name id message-id (first params))
|
||||
|
||||
(= method "wallet_addEthereumChain")
|
||||
(eip3085/handle-add-ethereum-chain cofx dapp-name id message-id (first params))
|
||||
|
||||
:else
|
||||
{:browser/call-rpc [payload
|
||||
#(re-frame/dispatch [:browser.callback/call-rpc
|
||||
{:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:error %1
|
||||
:result %2}])]}))))
|
||||
|
||||
(fx/defn handle-no-permissions [cofx {:keys [method id]} message-id]
|
||||
(if (= method "eth_accounts")
|
||||
@@ -419,7 +429,7 @@
|
||||
[{: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 payload message-id)))
|
||||
(web3-send-async cofx dapp-name payload message-id)))
|
||||
|
||||
(fx/defn handle-scanned-qr-code
|
||||
{:events [:browser.bridge.callback/qr-code-scanned]}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
;reference https://eips.ethereum.org/EIPS/eip-3085 EIP-3085: Wallet Add Ethereum Chain RPC Method (`wallet_addEthereumChain`)
|
||||
(ns status-im.browser.eip3085
|
||||
(:require [status-im.constants :as constants]
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.utils.fx :as fx]
|
||||
[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
|
||||
{:events [:eip3085/send-success-call-to-bridge]}
|
||||
[_ id messageId]
|
||||
{:browser/send-to-bridge {:type constants/web3-send-async-callback
|
||||
:messageId messageId
|
||||
:result {:jsonrpc "2.0"
|
||||
:id (int id)
|
||||
:result nil}}})
|
||||
|
||||
(fx/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)
|
||||
::json-rpc/call [{:method "settings_saveSetting"
|
||||
:params [:networks/networks (vals new-networks)]
|
||||
:on-success #(re-frame/dispatch [:eip3085/send-success-call-to-bridge cofx id message-id])
|
||||
:on-error #(log/error "failed to perform settings_saveSetting" %)}]
|
||||
:dispatch [:bottom-sheet/hide]})
|
||||
|
||||
(fx/defn deny-permission
|
||||
{:events [:eip3085.ui/dapp-permission-denied]}
|
||||
[_ message-id _]
|
||||
{:browser/send-to-bridge {:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:error {:code 4001
|
||||
:message "User rejected the request."}}
|
||||
:dispatch [:bottom-sheet/hide]})
|
||||
|
||||
(fx/defn handle-add-ethereum-chain
|
||||
{:events [:eip3085/handle-add-ethereum-chain]}
|
||||
[{{:networks/keys [networks] :as db} :db :as cofx}
|
||||
dapp-name id message-id {:keys [chainId blockExplorerUrls chainName iconUrls nativeCurrency rpcUrls] :as params}]
|
||||
(let [manage {:name {:value chainName}
|
||||
:symbol {:value (:symbol nativeCurrency)}
|
||||
:url {:value (first rpcUrls)}
|
||||
:network-id {:value chainId}
|
||||
:chain {:value :custom}}]
|
||||
(if (network/valid-manage? manage)
|
||||
(let [{:keys [name url chain network-id symbol]} manage
|
||||
random-id (string/replace (random/id) "-" "")
|
||||
network (network/new-network random-id
|
||||
(:value name)
|
||||
(:value symbol)
|
||||
(:value url)
|
||||
(:value chain)
|
||||
(:value network-id))
|
||||
new-networks (assoc networks random-id network)
|
||||
params (assoc params :new-networks new-networks :id id :new-network network)]
|
||||
(if (network/chain-id-available? networks network)
|
||||
{:dispatch [:bottom-sheet/show-sheet {:content (fn []
|
||||
[sheet/permissions-panel dapp-name message-id params])}]}
|
||||
(send-success-call-to-bridge cofx id message-id)))
|
||||
{:browser/send-to-bridge {:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:error {:code -32602
|
||||
:message "invalid network parameters"}}})))
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
;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]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.ui.screens.browser.eip3326.sheet :as sheet]))
|
||||
|
||||
(fx/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
|
||||
:messageId message-id
|
||||
:error {:code 4001
|
||||
:message "User rejected the request."}}
|
||||
:dispatch [:bottom-sheet/hide]})
|
||||
|
||||
(fx/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)
|
||||
networks (vals (get-in db [:networks/networks]))
|
||||
exist-chain-ids (set (map ethereum/network->chain-id networks))
|
||||
current-chain-id (ethereum/chain-id db)]
|
||||
(if (exist-chain-ids target-chain-id)
|
||||
(if (= current-chain-id target-chain-id)
|
||||
{:browser/send-to-bridge {:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:result {:jsonrpc "2.0"
|
||||
:id (int id)
|
||||
:result nil}}}
|
||||
(let [target-network (first (filter #(= (ethereum/network->chain-id %1) target-chain-id) networks))
|
||||
target-network-id (:id target-network)
|
||||
current-network (ethereum/current-network db)
|
||||
network-from (:name current-network)
|
||||
network-to (:name target-network)
|
||||
params (assoc params :target-network-id target-network-id :network-from network-from :network-to network-to)]
|
||||
{:dispatch [:bottom-sheet/show-sheet {:content (fn []
|
||||
[sheet/permissions-panel dapp-name message-id params])}]}))
|
||||
{:browser/send-to-bridge {:type constants/web3-send-async-callback
|
||||
:messageId message-id
|
||||
:error {:code 4902
|
||||
:message (str "Unrecognized chain ID: " target-chain-id ". Try adding the chain using wallet_addEthereumChain first.")}}})))
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
(ns status-im.chat.models.delete-message-for-me
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ethereum.json-rpc :as json-rpc]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.utils.fx :as fx]
|
||||
[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-for-me-undoable-till)))
|
||||
|
||||
(defn- update-db-delete-locally
|
||||
"Delete message for me 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-for-me? true
|
||||
:deleted-for-me-undoable-till (+ (datetime/timestamp)
|
||||
undo-time-limit-ms))))
|
||||
|
||||
(defn- update-db-undo-locally
|
||||
"Restore deleted-for-me message if called within timelimit"
|
||||
[db chat-id message-id]
|
||||
(let [{:keys [deleted-for-me? deleted-for-me-undoable-till]}
|
||||
(get-in db [:messages chat-id message-id])]
|
||||
(if (and deleted-for-me?
|
||||
(> deleted-for-me-undoable-till (datetime/timestamp)))
|
||||
(update-in db
|
||||
[:messages chat-id message-id]
|
||||
dissoc
|
||||
:deleted-for-me?
|
||||
:deleted-for-me-undoable-till)
|
||||
(update-db-clear-undo-timer db chat-id message-id))))
|
||||
|
||||
(fx/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]
|
||||
(when (get-in db [:messages chat-id message-id])
|
||||
{:db (update-db-delete-locally db chat-id message-id undo-time-limit-ms)
|
||||
:utils/dispatch-later [{:dispatch [:chat.ui/delete-message-for-me-and-sync
|
||||
{:chat-id chat-id
|
||||
:message-id message-id}]
|
||||
:ms undo-time-limit-ms}]}))
|
||||
|
||||
(fx/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])
|
||||
{:db (update-db-undo-locally db chat-id message-id)}))
|
||||
|
||||
(fx/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])
|
||||
{:db (update-db-clear-undo-timer db chat-id message-id)
|
||||
::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
|
||||
%])}]}))
|
||||
@@ -0,0 +1,115 @@
|
||||
(ns status-im.chat.models.delete-message-for-me-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.chat.models.delete-message-for-me :as
|
||||
delete-message-for-me]
|
||||
[status-im.utils.datetime :as datetime]))
|
||||
|
||||
(defonce mid "message-id")
|
||||
(defonce cid "chat-id")
|
||||
|
||||
(deftest delete-for-me
|
||||
(let [db {:messages {cid {mid {:id mid}}}}
|
||||
message {:message-id mid :chat-id cid}]
|
||||
(testing "delete for me"
|
||||
(let [expected {:db {:messages {"chat-id" {"message-id"
|
||||
{:id "message-id"
|
||||
:deleted-for-me? true}}}}
|
||||
:utils/dispatch-later
|
||||
[{:dispatch [:chat.ui/delete-message-for-me-and-sync
|
||||
{:chat-id "chat-id" :message-id "message-id"}]
|
||||
:ms 1000}]}
|
||||
result (delete-message-for-me/delete {:db db} message 1000)
|
||||
timestamp (+ (datetime/timestamp) 1000)]
|
||||
(is (= (update-in result [:db :messages "chat-id" "message-id"] dissoc :deleted-for-me-undoable-till)
|
||||
expected))
|
||||
(is (-> (get-in result [:db :messages "chat-id" "message-id" :deleted-for-me-undoable-till])
|
||||
(- timestamp)
|
||||
js/Math.abs
|
||||
(< 10)))))
|
||||
(testing "should return nil if message 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}}}}
|
||||
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))
|
||||
|
||||
expected {:db {:messages {"chat-id" {"message-id"
|
||||
{:id "message-id"}}}}}]
|
||||
(is (= (delete-message-for-me/undo {:db db} message) expected))))
|
||||
(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))
|
||||
|
||||
expected {:db {:messages {"chat-id" {"message-id"
|
||||
{:id "message-id"
|
||||
:deleted-for-me? true}}}}}]
|
||||
(is (= (delete-message-for-me/undo {:db db} message) expected))))
|
||||
(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))
|
||||
|
||||
expected {:db {:messages {"chat-id" {"message-id"
|
||||
{:id "message-id"
|
||||
:deleted-for-me? true}}}}}]
|
||||
(is (= (delete-message-for-me/undo {:db db} message) expected))))
|
||||
(testing "should return nil if message 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 {"chat-id" {"message-id" {:id "message-id"}}}}
|
||||
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 {"chat-id" {"message-id" {:id "message-id"}}}}
|
||||
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 in db"
|
||||
(is (= (delete-message-for-me/delete-and-sync {:db {:messages []}}
|
||||
message)
|
||||
nil)))))
|
||||
@@ -185,7 +185,7 @@
|
||||
[alias name nickname]))
|
||||
|
||||
(defn add-searchable-phrases-to-contact
|
||||
[{:keys [alias name added? blocked? identicon public-key nickname]} community-chat?]
|
||||
[{:keys [alias name added? blocked? identicon public-key nickname ens-verified]} community-chat?]
|
||||
(when (and alias
|
||||
(not (string/blank? alias))
|
||||
(or name
|
||||
@@ -194,11 +194,12 @@
|
||||
community-chat?)
|
||||
(not blocked?))
|
||||
(add-searchable-phrases
|
||||
{:alias alias
|
||||
:name (or (utils/safe-replace name ".stateofus.eth" "") alias)
|
||||
:identicon identicon
|
||||
:nickname nickname
|
||||
:public-key public-key})))
|
||||
{:alias alias
|
||||
:name (or (and ens-verified (utils/safe-replace name ".stateofus.eth" "")) alias)
|
||||
:identicon identicon
|
||||
:nickname nickname
|
||||
:ens-verified ens-verified
|
||||
:public-key public-key})))
|
||||
|
||||
(defn mentionable-contacts [contacts]
|
||||
(reduce
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[status-im.data-store.pin-messages :as data-store.pin-messages]
|
||||
[status-im.utils.fx :as fx]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.transport.message.protocol :as protocol]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(fx/defn handle-failed-loading-pin-messages
|
||||
@@ -73,7 +74,8 @@
|
||||
{:events [::send-pin-message]}
|
||||
[{:keys [db] :as cofx} {:keys [chat-id message-id pinned] :as pin-message}]
|
||||
(let [current-public-key (get-in db [:multiaccount :public-key])
|
||||
message (merge pin-message {:pinned-by current-public-key})]
|
||||
message (merge pin-message {:pinned-by current-public-key})
|
||||
preferred-name (get-in db [:multiaccount :preferred-name])]
|
||||
(fx/merge cofx
|
||||
{:db (cond-> db
|
||||
pinned
|
||||
@@ -86,7 +88,12 @@
|
||||
(update-in [:pin-messages chat-id] dissoc message-id)))}
|
||||
(data-store.pin-messages/send-pin-message {:chat-id (pin-message :chat-id)
|
||||
:message_id (pin-message :message-id)
|
||||
:pinned (pin-message :pinned)}))))
|
||||
:pinned (pin-message :pinned)})
|
||||
(when pinned
|
||||
(protocol/send-chat-messages [{:chat-id (pin-message :chat-id)
|
||||
:content-type constants/content-type-pin
|
||||
:response-to (pin-message :message-id)
|
||||
:ens-name preferred-name}])))))
|
||||
|
||||
(fx/defn load-pin-messages
|
||||
{:events [::load-pin-messages]}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
(def ^:const content-type-community 9)
|
||||
(def ^:const content-type-gap 10)
|
||||
(def ^:const content-type-contact-request 11) ;; TODO: temp, will be removed
|
||||
(def ^:const content-type-pin 13)
|
||||
|
||||
(def ^:const contact-request-state-none 0)
|
||||
(def ^:const contact-request-state-mutual 1)
|
||||
@@ -176,6 +177,7 @@
|
||||
(def ^:const docs-link "https://status.im/docs/")
|
||||
(def ^:const principles-link "https://our.status.im/our-principles/")
|
||||
|
||||
(def ^:const activity-center-notification-type-no-type 0)
|
||||
(def ^:const activity-center-notification-type-one-to-one-chat 1)
|
||||
(def ^:const activity-center-notification-type-private-group-chat 2)
|
||||
(def ^:const activity-center-notification-type-mention 3)
|
||||
@@ -183,6 +185,13 @@
|
||||
(def ^:const activity-center-notification-type-contact-request 5)
|
||||
(def ^:const activity-center-notification-type-contact-request-retracted 6)
|
||||
|
||||
;; TODO: Replace with correct enum values once status-go implements them.
|
||||
(def ^:const activity-center-notification-type-admin 66610)
|
||||
(def ^:const activity-center-notification-type-identity-verification 66611)
|
||||
(def ^:const activity-center-notification-type-tx 66612)
|
||||
(def ^:const activity-center-notification-type-membership 66613)
|
||||
(def ^:const activity-center-notification-type-system 66614)
|
||||
|
||||
(def ^:const visibility-status-unknown 0)
|
||||
(def ^:const visibility-status-automatic 1)
|
||||
(def ^:const visibility-status-dnd 2)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
:quotedMessage :quoted-message
|
||||
:outgoingStatus :outgoing-status
|
||||
:audioDurationMs :audio-duration-ms
|
||||
:deletedForMe :deleted-for-me?
|
||||
:new :new?})
|
||||
|
||||
(update :quoted-message clojure.set/rename-keys {:parsedText :parsed-text :communityId :community-id})
|
||||
|
||||
@@ -328,8 +328,9 @@
|
||||
;; Information Box
|
||||
|
||||
(def closable-information-boxes
|
||||
[{:id :ens-banner
|
||||
:global? true}]) ;; global? - close information box across all profiles
|
||||
"[{:id information box id
|
||||
:global? true/false (close information box across all profiles)}]"
|
||||
[])
|
||||
|
||||
(defn information-box-id-hash [id public-key global?]
|
||||
(if global?
|
||||
|
||||
@@ -55,13 +55,16 @@
|
||||
:podcasts (js/require "../resources/images/ui/podcasts.png")})
|
||||
|
||||
(def mock-images
|
||||
{:photo1 (js/require "../resources/images/mock/photo1.png")
|
||||
:photo2 (js/require "../resources/images/mock/photo2.png")
|
||||
:photo3 (js/require "../resources/images/mock/photo3.png")
|
||||
:community-banner (js/require "../resources/images/mock/community-banner.png")
|
||||
:community-logo (js/require "../resources/images/mock/community-logo.png")
|
||||
:gif (js/require "../resources/images/mock/gif.png")
|
||||
:sticker (js/require "../resources/images/mock/sticker.png")})
|
||||
{:photo1 (js/require "../resources/images/mock/photo1.png")
|
||||
:photo2 (js/require "../resources/images/mock/photo2.png")
|
||||
:photo3 (js/require "../resources/images/mock/photo3.png")
|
||||
:community-banner (js/require "../resources/images/mock/community-banner.png")
|
||||
:community-logo (js/require "../resources/images/mock/community-logo.png")
|
||||
:gif (js/require "../resources/images/mock/gif.png")
|
||||
:sticker (js/require "../resources/images/mock/sticker.png")
|
||||
:user-picture-female2 (js/require "../resources/images/mock/user_picture_female2.png")
|
||||
:user-picture-male4 (js/require "../resources/images/mock/user_picture_male4.png")
|
||||
:user-picture-male5 (js/require "../resources/images/mock/user_picture_male5.png")})
|
||||
|
||||
(defn get-theme-image [k]
|
||||
(get ui (when (colors/dark?) (keyword (str (name k) "-dark"))) (get ui k)))
|
||||
|
||||
@@ -6,29 +6,36 @@
|
||||
[clojure.string :as string]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/notifications-read
|
||||
(fn [db]
|
||||
(get-in db [:activity-center :notifications-read :data])))
|
||||
:activity-center/notifications
|
||||
:<- [:activity-center]
|
||||
(fn [activity-center]
|
||||
(:notifications activity-center)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/notifications-unread
|
||||
(fn [db]
|
||||
(get-in db [:activity-center :notifications-unread :data])))
|
||||
:activity-center/filter-status
|
||||
:<- [:activity-center]
|
||||
(fn [activity-center]
|
||||
(get-in activity-center [:filter :status])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/current-status-filter
|
||||
(fn [db]
|
||||
(get-in db [:activity-center :current-status-filter])))
|
||||
:activity-center/filter-type
|
||||
:<- [:activity-center]
|
||||
(fn [activity-center]
|
||||
(get-in activity-center [:filter :type] constants/activity-center-notification-type-no-type)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/notifications-per-read-status
|
||||
:<- [:activity-center/notifications-read]
|
||||
:<- [:activity-center/notifications-unread]
|
||||
:<- [:activity-center/current-status-filter]
|
||||
(fn [[notifications-read notifications-unread status-filter]]
|
||||
(if (= status-filter :unread)
|
||||
notifications-unread
|
||||
notifications-read)))
|
||||
:activity-center/filtered-notifications
|
||||
:<- [:activity-center/filter-type]
|
||||
:<- [:activity-center/filter-status]
|
||||
:<- [:activity-center/notifications]
|
||||
(fn [[filter-type filter-status notifications]]
|
||||
(get-in notifications [filter-type filter-status :data])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/filter-status-unread-enabled?
|
||||
:<- [:activity-center/filter-status]
|
||||
(fn [filter-status]
|
||||
(= :unread filter-status)))
|
||||
|
||||
(defn- group-notifications-by-date
|
||||
[notifications]
|
||||
@@ -64,4 +71,4 @@
|
||||
(map #(assoc %
|
||||
:timestamp (or (:timestamp %) (:timestamp (or (:message %) (:last-message %))))
|
||||
:contact (multiaccounts/contact-by-identity contacts (get-in % [:message :from])))
|
||||
supported-notifications)))))
|
||||
supported-notifications)))))
|
||||
|
||||
@@ -228,6 +228,7 @@
|
||||
|
||||
(reg-root-key-sub :activity.center/notifications :activity.center/notifications)
|
||||
(reg-root-key-sub :activity.center/notifications-count :activity.center/notifications-count)
|
||||
(reg-root-key-sub :activity-center :activity-center)
|
||||
|
||||
(reg-root-key-sub :bug-report/description-error :bug-report/description-error)
|
||||
(reg-root-key-sub :bug-report/details :bug-report/details)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:refer (SafeAreaProvider SafeAreaInsetsContext)]
|
||||
["@react-native-community/clipboard" :default Clipboard]
|
||||
["react-native-linear-gradient" :default LinearGradient]
|
||||
["@react-native-community/masked-view" :default MaskedView]
|
||||
["react-native-navigation" :refer (Navigation)]
|
||||
["react-native-fast-image" :as FastImage]
|
||||
["@react-native-community/blur" :as blur])
|
||||
@@ -40,6 +41,8 @@
|
||||
|
||||
(def linear-gradient (reagent/adapt-react-class LinearGradient))
|
||||
|
||||
(def masked-view (reagent/adapt-react-class MaskedView))
|
||||
|
||||
(def blur-view (reagent/adapt-react-class (.-BlurView blur)))
|
||||
|
||||
(defn valid-source? [source]
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
(ns status-im.ui.screens.activity-center.views
|
||||
(:require [quo.components.animated.pressable :as animation]
|
||||
[quo.design-system.colors :as quo.colors]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.notifications.activity-logs :as activity-logs]
|
||||
[quo2.components.tabs.tabs :as tabs]
|
||||
[quo2.components.tags.context-tags :as context-tags]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ui.components.topbar :as topbar]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.utils.handlers :refer [<sub >evt]]))
|
||||
|
||||
@@ -28,8 +30,8 @@
|
||||
[{:keys [type]}]
|
||||
(case type
|
||||
constants/activity-center-notification-type-contact-request
|
||||
:add-user
|
||||
:placeholder))
|
||||
:main-icons2/add-user
|
||||
:main-icons2/placeholder))
|
||||
|
||||
(defn activity-context
|
||||
[{:keys [message last-message type]}]
|
||||
@@ -77,7 +79,7 @@
|
||||
nil))
|
||||
|
||||
(defn activity-pressable
|
||||
[notification & children]
|
||||
[notification activity]
|
||||
(case (get-in notification [:message :contact-request-state])
|
||||
constants/contact-request-message-state-accepted
|
||||
;; NOTE [2022-09-21]: We need to dispatch to
|
||||
@@ -85,14 +87,13 @@
|
||||
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen looks completely
|
||||
;; broken if it has never been opened before for the accepted contact.
|
||||
[animation/pressable {:on-press #(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}])}
|
||||
children]
|
||||
[:<> children]))
|
||||
activity]
|
||||
activity))
|
||||
|
||||
(defn render-notification
|
||||
[notification index]
|
||||
[rn/view {:flex 1
|
||||
:flex-direction :column
|
||||
:margin-top (if (= 0 index) 0 4)}
|
||||
[rn/view {:margin-top (if (= 0 index) 0 4)
|
||||
:padding-horizontal 20}
|
||||
[activity-pressable notification
|
||||
[activity-logs/activity-log
|
||||
(merge {:context (activity-context notification)
|
||||
@@ -104,27 +105,108 @@
|
||||
:unread? (not (:read notification))}
|
||||
(activity-buttons notification))]]])
|
||||
|
||||
(defn notifications-list
|
||||
(defn filter-selector-read-toggle
|
||||
[]
|
||||
(let [notifications (<sub [:activity-center/notifications-per-read-status])]
|
||||
[rn/flat-list {:style {:padding-horizontal 8}
|
||||
:data notifications
|
||||
:key-fn :id
|
||||
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
|
||||
:render-fn render-notification}]))
|
||||
(let [unread-filter-enabled? (<sub [:activity-center/filter-status-unread-enabled?])]
|
||||
;; TODO: Replace the button by a Filter Selector component once available for use.
|
||||
[button/button {:icon true
|
||||
:type (if unread-filter-enabled? :primary :outline)
|
||||
:size 32
|
||||
:on-press #(>evt [:activity-center.notifications/fetch-first-page
|
||||
{:filter-status (if unread-filter-enabled?
|
||||
:read
|
||||
:unread)}])}
|
||||
:main-icons2/unread]))
|
||||
|
||||
(defn activity-center []
|
||||
;; TODO(2022-10-07): The empty state is still under design analysis, so we
|
||||
;; shouldn't even care about translations at this point. A placeholder box is
|
||||
;; used instead of an image.
|
||||
(defn empty-tab
|
||||
[]
|
||||
[rn/view {:style {:align-items :center
|
||||
:flex 1
|
||||
:justify-content :center
|
||||
:padding-vertical 12}}
|
||||
[rn/view {:style {:background-color colors/neutral-80
|
||||
:height 120
|
||||
:margin-bottom 20
|
||||
:width 120}}]
|
||||
[text/text {:size :paragraph-1
|
||||
:style {:padding-bottom 2}
|
||||
:weight :semi-bold}
|
||||
"No notifications"]
|
||||
[text/text {:size :paragraph-2}
|
||||
"Your notifications will be here"]])
|
||||
|
||||
(defn tabs
|
||||
[]
|
||||
(let [filter-type (<sub [:activity-center/filter-type])]
|
||||
[tabs/scrollable-tabs {:size 32
|
||||
:style {:padding-left 20}
|
||||
:fade-end-percentage 0.79
|
||||
:scroll-on-press? true
|
||||
:fade-end? true
|
||||
:on-change #(>evt [:activity-center.notifications/fetch-first-page {:filter-type %}])
|
||||
:default-active filter-type
|
||||
:data [{:id constants/activity-center-notification-type-no-type
|
||||
:label (i18n/label :t/all)}
|
||||
{:id constants/activity-center-notification-type-admin
|
||||
:label (i18n/label :t/admin)}
|
||||
{:id constants/activity-center-notification-type-mention
|
||||
:label (i18n/label :t/mentions)}
|
||||
{:id constants/activity-center-notification-type-reply
|
||||
:label (i18n/label :t/replies)}
|
||||
{:id constants/activity-center-notification-type-contact-request
|
||||
:label (i18n/label :t/contact-requests)}
|
||||
{:id constants/activity-center-notification-type-identity-verification
|
||||
:label (i18n/label :t/identity-verification)}
|
||||
{:id constants/activity-center-notification-type-tx
|
||||
:label (i18n/label :t/transactions)}
|
||||
{:id constants/activity-center-notification-type-membership
|
||||
:label (i18n/label :t/membership)}
|
||||
{:id constants/activity-center-notification-type-system
|
||||
:label (i18n/label :t/system)}]}]))
|
||||
|
||||
(defn header
|
||||
[]
|
||||
(let [screen-padding 20]
|
||||
;; TODO: Remove temporary (and old) background color when the screen and
|
||||
;; header are properly blurred.
|
||||
[rn/view {:background-color (:ui-background @quo.colors/theme)}
|
||||
[button/button {:icon true
|
||||
:type :grey
|
||||
:size 32
|
||||
:style {:margin-vertical 12
|
||||
:margin-left screen-padding}
|
||||
:on-press #(>evt [:navigate-back])}
|
||||
:main-icons2/close]
|
||||
[text/text {:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style {:padding-horizontal screen-padding
|
||||
:padding-vertical 12}}
|
||||
(i18n/label :t/notifications)]
|
||||
[rn/view {:flex-direction :row
|
||||
:padding-vertical 12}
|
||||
[rn/view {:flex 1
|
||||
:align-self :stretch}
|
||||
[tabs]]
|
||||
[rn/view {:flex-grow 0
|
||||
:margin-left 16
|
||||
:padding-right screen-padding}
|
||||
[filter-selector-read-toggle]]]]))
|
||||
|
||||
(defn activity-center
|
||||
[]
|
||||
(reagent/create-class
|
||||
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])
|
||||
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page])
|
||||
:reagent-render
|
||||
(fn []
|
||||
[:<>
|
||||
[topbar/topbar {:navigation {:on-press #(>evt [:navigate-back])}
|
||||
:title (i18n/label :t/activity)}]
|
||||
;; TODO(ilmotta): Temporary solution to switch between read/unread
|
||||
;; notifications while the Design team works on the mockups.
|
||||
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])}
|
||||
"Unread"]
|
||||
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :read}])}
|
||||
"Read"]
|
||||
[notifications-list]])}))
|
||||
(let [notifications (<sub [:activity-center/filtered-notifications])]
|
||||
[rn/flat-list {:content-container-style {:flex-grow 1}
|
||||
:data notifications
|
||||
:empty-component [empty-tab]
|
||||
:header [header]
|
||||
:key-fn :id
|
||||
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
|
||||
:render-fn render-notification
|
||||
:sticky-header-indices [0]}]))}))
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
(ns status-im.ui.screens.browser.eip3085.sheet
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.browser.styles :as styles]
|
||||
[quo.design-system.colors :as colors]
|
||||
[quo.core :as quo]
|
||||
[status-im.ui.components.copyable-text :as copyable-text])
|
||||
(:require-macros [status-im.utils.views :as views]))
|
||||
|
||||
(views/defview permissions-panel [dapp-name message-id params]
|
||||
(views/letsubs [{:keys [dapp? dapp]} [:get-current-browser]]
|
||||
[react/view {}
|
||||
[react/view styles/permissions-panel-icons-container
|
||||
(if dapp?
|
||||
[chat-icon.screen/dapp-icon-permission dapp 40]
|
||||
[react/view styles/permissions-panel-dapp-icon-container
|
||||
[icons/icon :main-icons/dapp {:color colors/gray}]])
|
||||
[react/view {:margin-left 8 :margin-right 4}
|
||||
[react/view styles/dot]]
|
||||
[react/view {:margin-right 4}
|
||||
[react/view styles/dot]]
|
||||
[react/view {:margin-right 8}
|
||||
[react/view styles/dot]]
|
||||
[react/view styles/permissions-panel-ok-icon-container
|
||||
[icons/icon :tiny-icons/tiny-check styles/permissions-panel-ok-ico]]
|
||||
[react/view {:margin-left 8 :margin-right 4}
|
||||
[react/view styles/dot]]
|
||||
[react/view {:margin-right 4}
|
||||
[react/view styles/dot]]
|
||||
[react/view {:margin-right 8}
|
||||
[react/view styles/dot]]
|
||||
[react/view styles/permissions-panel-wallet-icon-container
|
||||
[icons/icon :main-icons/wallet {:color colors/white}]]]
|
||||
[react/text {:style styles/permissions-panel-title-label :number-of-lines 2}
|
||||
(str "\"" dapp-name "\" Allow this site to add a network?")]
|
||||
[react/text {:style styles/permissions-panel-description-label :number-of-lines 4} "This will allow this network to be used within Status.Status does not verify custom networks.Learn about scams and network security risks."]
|
||||
[react/scroll-view
|
||||
[copyable-text/copyable-text-view
|
||||
{:copied-text (:name (:new-network params))}
|
||||
[quo/list-item
|
||||
{:size :small
|
||||
:accessibility-label :network-name
|
||||
:title "Network Name"
|
||||
:accessory :text
|
||||
:accessory-text (:name (:new-network params))}]]
|
||||
[copyable-text/copyable-text-view
|
||||
{:copied-text (get-in params [:new-network :config :UpstreamConfig :URL])}
|
||||
[quo/list-item
|
||||
{:size :small
|
||||
:accessibility-label :network-url
|
||||
:title "Network URL"
|
||||
:accessory :text
|
||||
:accessory-text (get-in params [:new-network :config :UpstreamConfig :URL])}]]
|
||||
[copyable-text/copyable-text-view
|
||||
{:copied-text (str (get-in params [:new-network :config :NetworkId]))}
|
||||
[quo/list-item
|
||||
{:size :small
|
||||
:accessibility-label :network-id
|
||||
:title "Chain ID"
|
||||
:accessory :text
|
||||
:accessory-text (str (get-in params [:new-network :config :NetworkId]))}]]]
|
||||
[react/view {:style {:flex-direction :row
|
||||
:justify-content :center
|
||||
:margin-horizontal 8
|
||||
:margin-top 24}}
|
||||
[react/view {:flex 1
|
||||
:margin-horizontal 8}
|
||||
[quo/button
|
||||
{:theme :negative
|
||||
:on-press #(re-frame/dispatch [:eip3085.ui/dapp-permission-denied message-id params])}
|
||||
(i18n/label :t/deny)]]
|
||||
[react/view {:flex 1
|
||||
:margin-horizontal 8}
|
||||
[quo/button
|
||||
{:theme :positive
|
||||
:style {:margin-horizontal 8}
|
||||
:on-press #(re-frame/dispatch [:eip3085.ui/dapp-permission-allowed message-id params])}
|
||||
(i18n/label :t/allow)]]]]))
|
||||