Compare commits

..
Author SHA1 Message Date
pavloburykh 9be30f69e4 Updated go for testing fix/pairing-server-race 2022-10-17 14:37:16 +03:00
Jamie Caprani 62c6a0ea72 feat: add drawers - action drawers component to quo2 library (#14157) 2022-10-17 03:14:39 -07:00
John Ngei 7f54427df4 Fix: refactored token token to fix a bug (#14152) 2022-10-17 11:18:24 +03:00
Parvesh Monu ac9fb2fe4d fix ens name turns into public key while editing message (#14149) 2022-10-14 17:35:36 +05:30
yqrashawn 509b8bf3e1 fix: remove strict equal check on timer in delete for me test (#14159)
fix #14155
2022-10-14 11:06:41 +02:00
frank a74a44e492 Allow dApps to suggest change of RPC (EIP-3085 EIP-3326) (#13716) 2022-10-14 10:32:55 +02:00
Omar Basem af3ba74e30 feat: pinned messages new ui (#14147)
* feat: pinned messages new ui
2022-10-13 22:27:56 +04:00
Omar Basem 5492d502fc feat: autocomplete mentions new ui (#14142)
* feat: autocomplete mentions new ui
2022-10-13 21:59:19 +04:00
Parvesh Monu 7368478033 Implementation of Floating Shell Button (#14154) 2022-10-13 16:59:34 +05:30
yqrashawn bc050af9ed feat: delete for me logic without UI (#14007) 2022-10-12 21:21:20 +08:00
jakub d6a8f912ea nix: upgrade nixpkgs to latest nixos-unstable
Notable upgrades:

* Go `1.17.11` to `1.18.6`
* NodeJS `16.15.0` to `16.17.1`
* Clojure `1.11.1.1139` to `1.11.1.1165`
* Ruby Gem `3.2.26` to `3.3.20`
* Bundler `2.3.9` to `2.3.22`
* Git `2.36.1` to `2.37.3`
* Curl `7.83.1` to `7.85.0`
* OpenSSL `1.1.1o` to `3.0.5`
* PatchELF `0.14.5` to `0.15.0`
* Android SDK Platform Tools `33.0.1` to `33.0.2`

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-10-12 11:07:57 +02:00
jakub 6a23f64550 nix: re-add step to call bundler install
Was removed in:
https://github.com/status-im/status-jenkins-lib/pull/48

But only causes issues when version of Ruby Gems change.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-10-12 11:07:56 +02:00
jakub 4bbacf542c fastlane: update gems
Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-10-12 11:07:55 +02:00
Ibrahem Khalil 6991f213af [#13569] Page Nav (#13773) 2022-10-11 15:01:28 +02:00
flexsurfer 35eae4587b clean deps and fix warnings (#14141) 2022-10-11 12:20:37 +02:00
Parvesh Monu 46fd30597a Implementation of Dynamic Button Component (#14127) 2022-10-11 14:10:54 +05:30
Icaro Motta af5f979443 [#13968] Fetch and reconcile notifications by type and read status (#14125)
- Extract tabs component from the main screen component to reduce rendering cost when switching tabs.
- Display empty state component when there are no notifications.
- Fix screen flickering due to quickly flushing and filling the db state.
- Display top bar as a sticky header.
- Namespace icon keywords.
- Correctly sorts notifications during reconciliation.
- Remove warning about children without unique key.
2022-10-10 16:33:42 -03:00
Omar Basem adfb0ddb5d feat: reply to message new ui (#14098)
* feat: reply to messsage new ui
2022-10-10 19:55:02 +04:00
Mohamed Javid 0a282b3a47 [Feature] Added floating button to scroll down in chat (#14129)
* [Feature][#14063] Added floating button to scroll down in chat

* [Fix][#14063] Crash on pressing floating button to scroll down in chat
2022-10-10 22:33:53 +08:00
Roman Volosovskyi 5f98e96d23 Use a single geth.log file for all networks on iOS 2022-10-09 16:09:18 +02:00
Churikova Tetiana 823fce2457 e2e: exception and new custom network 2022-10-07 00:31:34 +02:00
Jamie Caprani 1d5e1479f7 chore: add components to communities overview (#14108) 2022-10-06 09:32:45 -07:00
erikseppanen c2c281d9c6 [13939] Implement system message into quo2 components (#14061) 2022-10-06 10:59:39 -04:00
Parvesh Monu 1f6ebbb5e8 remove ens banner component (#14112) 2022-10-06 12:31:07 +05:30
Icaro Motta 73983b2abd [#13967] Displays filtering tabs and read/unread filter (#14105) 2022-10-05 14:44:37 -03:00
Ibrahem Khalilandjakub 390ac858dc Update starting guide doc (#14107)
* Update starting guide doc

* Fix bad placement of tut

* formatting and style fixes

Signed-off-by: Jakub Sokołowski <jakub@status.im>

* add adb link

Signed-off-by: Jakub Sokołowski <jakub@status.im>
Co-authored-by: Jakub Sokołowski <jakub@status.im>
2022-10-05 15:39:28 +02:00
113 changed files with 2901 additions and 1007 deletions
+2
View File
@@ -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 -1
View File
@@ -1 +1 @@
1.20.0
1.20.2
+29 -4
View File
@@ -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`.
+18 -18
View File
@@ -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
+24 -24
View File
@@ -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"];
+3 -3
View File
@@ -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
@@ -172,12 +172,15 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
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];
@@ -185,6 +188,7 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
//[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];
@@ -422,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);
@@ -437,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];
+3
View File
@@ -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;
+6 -3
View File
@@ -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'
];
};
+8
View File
@@ -0,0 +1,8 @@
{ mkShell, bundler }:
mkShell {
buildInputs = [ bundler ];
shellHook = ''
bundle install --quiet --gemfile=fastlane/Gemfile
'';
}
-24
View File
@@ -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
'';
}
+8 -7
View File
@@ -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=";
};
+2 -2
View File
@@ -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
};
+1 -1
View File
@@ -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" ];
+1 -4
View File
@@ -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": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

+3
View File
@@ -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
+4 -3
View File
@@ -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}]]))
+34 -36
View File
@@ -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])]])))
@@ -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)])])
@@ -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))]]))
+132 -11
View File
@@ -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]])})])))))
+20 -21
View File
@@ -3,8 +3,7 @@
[quo.react-native :as rn]
[quo.theme :as theme]
[quo2.components.markdown.text :as text]
[status-im.ui.components.icons.icons :as icons]
[quo2.components.tags.tag :as tag]))
[status-im.ui.components.icons.icons :as icons]))
(def themes {:light {:background-color colors/neutral-20}
:dark {:background-color colors/neutral-80}})
@@ -77,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)]))
+7 -1
View File
@@ -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
+1 -1
View File
@@ -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"}]}
+11 -4
View File
@@ -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))}]])
+23 -3
View File
@@ -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,6 +35,7 @@
[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]
@@ -43,6 +47,7 @@
[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.navigation.page-nav :as page-nav]
[re-frame.core :as re-frame]))
(def screens-categories
@@ -63,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}]
@@ -76,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}]
@@ -96,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}]
@@ -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}]])
+106
View File
@@ -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}]])
+1 -2
View File
@@ -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]
+84 -56
View File
@@ -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?)})
+283 -68
View File
@@ -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))))))))
+27 -17
View File
@@ -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]}
+69
View File
@@ -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"}}})))
+43
View File
@@ -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)))))
+7 -6
View File
@@ -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
+9 -2
View File
@@ -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]}
+9
View File
@@ -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)
+1
View File
@@ -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})
+3 -2
View File
@@ -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?
+16 -29
View File
@@ -5,15 +5,12 @@
[status-im.utils.fx :as fx]
[status-im.wallet.core :as wallet]
["@react-native-community/netinfo" :default net-info]
[taoensso.timbre :as log]
[reagent.core :as reagent]))
[taoensso.timbre :as log]))
(fx/defn change-network-status
[{:keys [db] :as cofx} is-connected? reconnected?]
[{:keys [db] :as cofx} is-connected?]
(fx/merge cofx
(cond-> {:db (assoc db :network-status (if is-connected? :online :offline))}
reconnected? (assoc :dispatch-later [{:dispatch [:wallet-connect-legacy/get-connector-session-from-db]
:ms 1500}]))
{:db (assoc db :network-status (if is-connected? :online :offline))}
(when (and is-connected?
(or (not= (count (get-in db [:wallet :accounts]))
(count (get db :multiaccount/accounts)))
@@ -29,39 +26,29 @@
(fx/defn handle-network-info-change
{:events [::network-info-changed]}
[{:keys [db] :as cofx} {:keys [isConnected type details isInternetReachable app-went-offline?] :as state}]
[{:keys [db] :as cofx} {:keys [isConnected type details] :as state}]
(let [old-network-status (:network-status db)
old-network-type (:network/type db)
connectivity-status (if isConnected :online :offline)
status-changed? (not= connectivity-status old-network-status)
type-changed? (= type old-network-type)
_ (when (and (not isConnected)
(not isInternetReachable)
(not @app-went-offline?))
(swap! app-went-offline? not))
reconnected? (when (and isInternetReachable
@app-went-offline?)
(swap! app-went-offline? not)
true)]
status-changed? (= connectivity-status old-network-status)
type-changed? (= type old-network-type)]
(log/debug "[net-info]"
"old-network-status" old-network-status
"old-network-type" old-network-type
"old-network-status" old-network-status
"old-network-type" old-network-type
"connectivity-status" connectivity-status
"type" type
"details" details)
"type" type
"details" details)
(fx/merge cofx
(when (or status-changed? reconnected?)
(change-network-status isConnected reconnected?))
(when-not status-changed?
(change-network-status isConnected))
(when-not type-changed?
(change-network-type old-network-type type (:is-connection-expensive details))))))
(defn add-net-info-listener []
(let [app-went-offline? (reagent/atom false)]
(when net-info
(.addEventListener ^js net-info
#(re-frame/dispatch [::network-info-changed
(-> (js->clj % :keywordize-keys true)
(assoc :app-went-offline? app-went-offline?))])))))
(when net-info
(.addEventListener ^js net-info
#(re-frame/dispatch [::network-info-changed
(js->clj % :keywordize-keys true)]))))
(re-frame/reg-fx
::listen-to-network-info
+10 -7
View File
@@ -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)))
+25 -18
View File
@@ -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)))))
+1
View File
@@ -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)
+3
View File
@@ -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)]]]]))
@@ -0,0 +1,58 @@
(ns status-im.ui.screens.browser.eip3326.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]
[status-im.utils.debounce :as debounce]
[status-im.network.core :as network]
[quo.design-system.colors :as colors]
[quo.core :as quo])
(:require-macros [status-im.utils.views :as views]))
(views/defview permissions-panel [dapp-name message-id {:keys [network-from network-to target-network-id] :as 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 switch the network?")]
[react/text {:style styles/permissions-panel-description-label :number-of-lines 5}
(str "This will switch the selected network within Status to a previously added network:\n" network-from " -> " network-to "\nit will require login/logout")]
[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 [:eip3326.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 #(debounce/dispatch-and-chill [::network/connect-network-pressed target-network-id] 1000)}
(i18n/label :t/allow)]]]]))
@@ -31,7 +31,7 @@
(defn edit-message []
[rn/view {:style {:flex-direction :row :height 24}}
[rn/view {:style (styles/reply-content)}
[rn/view {:style (styles/reply-content false)}
[icons/icon :main-icons/edit-connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}]
[rn/view {:style {:position :absolute :left 36 :right 54 :top 3 :flex-direction :row :align-items :center}}
@@ -2,12 +2,14 @@
(:require [status-im.ui.components.icons.icons :as icons]
[quo.react-native :as rn]
[oops.core :refer [oget]]
[quo.react :as react]
[quo.react :as quo.react]
[quo.platform :as platform]
[quo.components.text :as text]
[quo.design-system.colors :as colors]
[quo2.foundations.colors :as quo2.colors]
[status-im.ui.screens.chat.components.style :as styles]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.screens.chat.components.reply :as reply]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.chat.constants :as chat.constants]
@@ -27,7 +29,7 @@
[quo.components.safe-area :as safe-area]))
(defn input-focus [text-input-ref]
(some-> ^js (react/current-ref text-input-ref) .focus))
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
(def panel->icons {:extensions :main-icons/commands
:images :main-icons/photo})
@@ -157,7 +159,7 @@
(quo.react/set-native-props sticker-ref #js {:width nil :right nil})))
(defn reset-input [refs chat-id]
(some-> ^js (react/current-ref (:text-input-ref refs)) .clear)
(some-> ^js (quo.react/current-ref (:text-input-ref refs)) .clear)
(swap! mentions-enabled update :render not)
(swap! input-texts dissoc chat-id))
@@ -204,11 +206,11 @@
(re-frame/reg-fx
:set-input-text
(fn [[chat-id text]]
;; We enable mentions
;; We enable mentions
(swap! mentions-enabled assoc chat-id true)
(on-text-change text chat-id)
;; We update the key so that we force a refresh of the text input, as those
;; are not ratoms
;; We update the key so that we force a refresh of the text input, as those
;; are not ratoms
(force-text-input-update!)))
(fx/defn set-input-text
@@ -216,16 +218,23 @@
as arguments and returns new fx. Always clear all validation messages."
{:events [:chat.ui.input/set-chat-input-text]}
[{:keys [db] :as cofx} text chat-id]
(let [text-with-mentions (mentions/->input-field text)
contacts (:contacts db)
hydrated-mentions (map (fn [[t mention :as e]]
(if (= t :mention)
[:mention (str "@" (multiaccounts/displayed-name
(or (get contacts mention)
{:public-key mention})))]
e)) text-with-mentions)
info (mentions/->info hydrated-mentions)]
{:set-input-text [chat-id text]
(let [text-with-mentions (mentions/->input-field text)
all-contacts (:contacts/contacts db)
chat (get-in db [:chats chat-id])
current-multiaccount (:multiaccount db)
mentionable-users (mentions/get-mentionable-users
chat all-contacts current-multiaccount nil)
hydrated-mentions (map
(fn [[t mention :as e]]
(if (= t :mention)
(let [mention (multiaccounts/displayed-name
(get mentionable-users mention))]
[:mention (if (string/starts-with? mention "@")
mention (str "@" mention))])
e)) text-with-mentions)
info (mentions/->info hydrated-mentions)
new-text (string/join (map second hydrated-mentions))]
{:set-input-text [chat-id new-text]
:db
(-> db
(assoc-in [:chats/cursor chat-id] (:mention-end info))
@@ -330,47 +339,46 @@
[idx item])
@(re-frame/subscribe [:chat/input-with-mentions]))]
^{:key (str idx "_" type "_" text)}
[rn/text (when (= type :mention) {:style {:color "#0DA4C9"}})
text])
[rn/text (when (= type :mention) {:style {:color quo2.colors/primary-50}}) text])
(get @input-texts chat-id))]))
(defn mention-item
[[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref]
(let [ens-name? (not= alias name)]
[list-item/list-item
(cond-> {:icon [photos/member-photo public-key]
:size :small
:text-size :small
:title
[text/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:size :small}
(if nickname
nickname
name)
(when nickname
[text/text
{:weight :regular
:color :secondary
:ellipsize-mode :tail
:size :small}
" "
(when ens-name?
"@")
name])]
:title-text-weight :medium
:on-press
(fn []
(re-frame/dispatch [:chat.ui/select-mention text-input-ref user]))}
[rn/touchable-opacity
{:on-press #(re-frame/dispatch [:chat.ui/select-mention text-input-ref user])}
ens-name?
(assoc :subtitle alias))]))
[list-item/list-item
(cond-> {:icon [photos/member-photo public-key]
:size :small
:text-size :small
:title
[text/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:size :small}
(if nickname
nickname
name)
(when nickname
[text/text
{:weight :regular
:color :secondary
:ellipsize-mode :tail
:size :small}
" "
(when ens-name?
"@")
name])]
:title-text-weight :medium}
ens-name?
(assoc :subtitle alias))]]))
(def chat-toolbar-height (reagent/atom nil))
(defn autocomplete-mentions [text-input-ref bottom]
(defn autocomplete-mentions-old [text-input-ref bottom]
(let [suggestions @(re-frame/subscribe [:chat/mention-suggestions])]
(when (seq suggestions)
(let [height (+ 16 (* 52 (min 4.5 (count suggestions))))]
@@ -388,6 +396,22 @@
:render-data text-input-ref
:render-fn mention-item}]]]))))
(defn autocomplete-mentions [suggestions]
[:f>
(fn []
(let [animation (reanimated/use-shared-value 0)]
(quo.react/effect! #(do
(reanimated/set-shared-value animation (reanimated/with-timing (if (seq suggestions) 0 200)))))
[reanimated/view {:style (reanimated/apply-animations-to-style
{:transform [{:translateY animation}]}
{:bottom 0 :position :absolute :z-index 5 :max-height 180})}
[list/flat-list
{:keyboardShouldPersistTaps :always
:data suggestions
:key-fn first
:render-fn mention-item
:content-container-style {:padding-bottom 12}}]]))])
(defn on-chat-toolbar-layout [^js ev]
(reset! chat-toolbar-height (-> ev .-nativeEvent .-layout .-height)))
@@ -461,12 +485,12 @@
:input-focus #(input-focus text-input-ref)
:set-active set-active-panel}])])]]]))))
(defn calculate-y [context keyboard-shown min-y max-y]
(defn calculate-y [context keyboard-shown min-y max-y added-value]
(if keyboard-shown
(if (= (:state @context) :max)
max-y
(if (< (:y @context) max-y)
(:y @context)
(+ (:y @context) added-value)
(do
(swap! context assoc :state :max)
max-y)))
@@ -474,6 +498,21 @@
(swap! context assoc :state :min)
min-y)))
(defn calculate-y-with-mentions [y max-y max-height chat-id suggestions reply]
(let [input-text (:input-text (get (<sub [:chat/inputs]) chat-id))
num-lines (count (string/split input-text "\n"))
text-height (* num-lines 22)
mentions-height (min 132 (+ 16 (* 46 (- (count suggestions) 1))))
should-translate (if (< (- max-height text-height) mentions-height) true false)
min-value (if-not reply mentions-height (+ mentions-height 44))
; translate value when mentions list appear while at bottom of expanded input sheet
mentions-translate-value (if should-translate (min min-value (- mentions-height (- max-height text-height))) mentions-height)]
(when (or (< y max-y) should-translate) mentions-translate-value)))
(defn get-y-value [context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply]
(let [y (calculate-y context keyboard-shown min-y max-y added-value)]
y (+ y (when (seq suggestions) (calculate-y-with-mentions y max-y max-height chat-id suggestions reply)))))
(defn get-bottom-sheet-gesture [context translate-y text-input-ref keyboard-shown min-y max-y shared-height max-height bg-opacity]
(-> (gesture/gesture-pan)
(gesture/on-start
@@ -554,30 +593,30 @@
:pdy 0 ;used for gesture
:state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max
:clear false})
keyboard-was-shown (atom false)]
keyboard-was-shown (atom false)
text-input-ref (quo.react/create-ref)
send-ref (quo.react/create-ref)
refs {:send-ref send-ref
:text-input-ref text-input-ref}]
(fn []
[:f>
(fn []
(let [text-input-ref (quo.react/create-ref)
send-ref (quo.react/create-ref)
refs {:send-ref send-ref
:text-input-ref text-input-ref}
(let [reply (<sub [:chats/reply-message])
suggestions (<sub [:chat/mention-suggestions])
{window-height :height} (rn/use-window-dimensions)
{:keys [keyboard-shown keyboard-height]} (rn/use-keyboard)
max-y (- window-height (if (> keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - defaul height
max-height (- max-y 56 (:bottom insets)) ; 56 - topbar height
y (calculate-y context keyboard-shown min-y max-y)
max-y (- window-height (if (> keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - default height
max-height (- max-y 56 (:bottom insets)) ; 56 - top-bar height
added-value (if (and (not (seq suggestions)) reply) 38 0) ; increased height of input box needed when reply
min-y (+ min-y (when reply 38))
y (get-y-value context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply)
translate-y (reanimated/use-shared-value 0)
shared-height (reanimated/use-shared-value min-y)
bg-opacity (reanimated/use-shared-value 0)
bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y text-input-ref keyboard-shown
min-y max-y shared-height max-height bg-opacity)
input-content-change (get-input-content-change context translate-y shared-height max-height
bg-opacity keyboard-shown min-y max-y)]
bg-opacity keyboard-shown min-y max-y)
bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y (:text-input-ref refs) keyboard-shown
min-y max-y shared-height max-height bg-opacity)]
(quo.react/effect! #(do
(when (and @keyboard-was-shown (not keyboard-shown))
(swap! context assoc :state :min))
@@ -597,26 +636,29 @@
(styles/new-input-bottom-sheet window-height))}
;handle
[rn/view {:style (styles/new-bottom-sheet-handle)}]
[rn/view {:style {:height (- max-y 80)}}
[reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply]
[rn/view {:style {:height (- max-y 80 added-value)}}
[text-input {:chat-id chat-id
:on-content-size-change input-content-change
:sending-image false
:refs refs
:set-active-panel #()}]]]]
;CONTROLS
[rn/view {:style (styles/new-bottom-sheet-controls insets)}
[quo2.button/button {:icon true :type :outline :size 32} :main-icons2/image]
[rn/view {:width 12}]
[quo2.button/button {:icon true :type :outline :size 32} :main-icons2/reaction]
[rn/view {:flex 1}]
;;SEND button
[rn/view {:ref send-ref :style (when-not (seq (get @input-texts chat-id)) {:width 0 :right -100})}
[quo2.button/button {:icon true :size 32 :accessibility-label :send-message-button
:on-press #(do (swap! context assoc :clear true)
(clear-input chat-id refs)
(re-frame/dispatch [:chat.ui/send-current-message]))}
:main-icons2/arrow-up]]]
(when-not (seq suggestions)
[rn/view {:style (styles/new-bottom-sheet-controls insets)}
[quo2.button/button {:icon true :type :outline :size 32} :main-icons2/image]
[rn/view {:width 12}]
[quo2.button/button {:icon true :type :outline :size 32} :main-icons2/reaction]
[rn/view {:flex 1}]
;;SEND button
[rn/view {:ref send-ref :style (when-not (seq (get @input-texts chat-id)) {:width 0 :right -100})}
[quo2.button/button {:icon true :size 32 :accessibility-label :send-message-button
:on-press #(do (swap! context assoc :clear true)
(clear-input chat-id refs)
(>evt [:chat.ui/send-current-message]))}
:main-icons2/arrow-up]]])
;black background
[reanimated/view {:style (reanimated/apply-animations-to-style
{:opacity bg-opacity}
(styles/new-bottom-sheet-background window-height))}]]))])))])
(styles/new-bottom-sheet-background window-height))}]
[autocomplete-mentions suggestions]]))])))])
@@ -81,15 +81,16 @@
[icons/icon :main-icons/close-circle {:container-style (styles/close-button)
:color (:icon-02 @quo.colors/theme)}]]]]))
(defn reply-message [{:keys [from identicon content-type contentType parsed-text content]} in-chat-input?]
; This component is also used for quoted pinned message as the UI is very similar
(defn reply-message [{:keys [from identicon content-type contentType parsed-text content]} in-chat-input? pin?]
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])
current-public-key @(re-frame/subscribe [:multiaccount/public-key])
content-type (or content-type contentType)]
[rn/view {:style {:flex-direction :row :height 24}}
[rn/view {:style (styles/reply-content)}
[icons/icon :main-icons/connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}]
[rn/view {:style {:position :absolute :left 34 :right 54 :top 3 :flex-direction :row :align-items :center}}
[rn/view {:style {:flex-direction :row :height (when-not pin? 24)}}
[rn/view {:style (styles/reply-content pin?)}
(when-not pin? [icons/icon :main-icons/connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}])
[rn/view {:style (styles/quoted-message pin?)}
[photos/member-photo from identicon 16]
[quo2.text/text {:weight :semi-bold
:size :paragraph-2
@@ -125,7 +126,7 @@
(defn send-image [images]
[rn/view {:style (styles/reply-container-image)}
[rn/scroll-view {:horizontal true
:style (styles/reply-content)}
:style (styles/reply-content false)}
(for [{:keys [uri]} (vals images)]
^{:key uri}
[rn/image {:source {:uri uri}
@@ -144,7 +145,8 @@
(when-not (= reply @had-reply)
(reset! had-reply reply)
(when reply
(js/setTimeout #(input-focus text-input-ref) 250))))
;; A setTimeout of 0 is necessary to ensure the statement is enqueued and will get executed ASAP.
(js/setTimeout #(input-focus text-input-ref) 0))))
(defn reply-message-wrapper-old [reply]
[rn/view {:style {:padding-horizontal 15
@@ -155,8 +157,6 @@
(defn reply-message-wrapper [reply]
[rn/view {:style {:padding-horizontal 15
:border-top-width 1
:border-top-color (:ui-01 @quo.colors/theme)
:padding-vertical 8}}
[reply-message reply true]])
@@ -168,10 +168,9 @@
(when reply
[reply-message-wrapper-old reply])))))
(defn reply-message-auto-focus-wrapper [text-input-ref]
(defn reply-message-auto-focus-wrapper [text-input-ref _]
(let [had-reply (atom nil)]
(fn []
(let [reply @(re-frame/subscribe [:chats/reply-message])]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply-message-wrapper reply])))))
(fn [_ reply]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply-message-wrapper reply]))))
@@ -108,11 +108,19 @@
:padding-horizontal 10
:flex 1})
(defn reply-content []
{:padding-horizontal 10
(defn reply-content [pin?]
{:padding-horizontal (when-not pin? 10)
:flex 1
:flex-direction :row})
(defn quoted-message [pin?]
(merge {:flex-direction :row
:align-items :center
:width "45%"}
(when-not pin? {:position :absolute
:left 34
:top 3})))
(defn contact-request-content []
{:flex 1
:flex-direction :row
@@ -143,7 +151,8 @@
:bottom bottom
:background-color (colors/get-color :ui-background)
:border-top-width 1
:border-top-color (colors/get-color :ui-01)})
:border-top-color (colors/get-color :ui-01)
:z-index 3})
(defn new-input-bottom-sheet [window-height]
(merge {:border-top-left-radius 20
@@ -155,7 +164,7 @@
:height window-height
:flex 1
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)
:z-index 1000}
:z-index 2}
(if platform/ios?
{:shadow-radius 16
:shadow-opacity 1
@@ -176,7 +185,7 @@
{:flex-direction :row
:padding-horizontal 20
:elevation 2
:z-index 2000
:z-index 2
:position :absolute
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)
;these 3 props play together, we need this magic to hide message text in the safe area
@@ -192,4 +201,4 @@
:bottom 0
:height window-height
:background-color quo2.colors/neutral-95-opa-70
:z-index 500})
:z-index 1})
@@ -4,7 +4,6 @@
[reagent.core :as reagent]
[goog.string :as gstring]
[status-im.audio.core :as audio]
[status-im.utils.fx :as fx]
[status-im.ui.screens.chat.styles.message.audio :as style]
[status-im.ui.components.animation :as anim]
[quo.design-system.colors :as colors]
@@ -184,15 +183,6 @@
:accessibility-label :play-pause-audio-message-button
:color color}]])))
(fx/defn on-background
{:events [:audio-message/on-background]}
[_]
(when (and @current-active-state-ref-ref
@@current-active-state-ref-ref)
(update-state {:state-ref @current-active-state-ref-ref
:message-id @current-player-message-id}))
nil)
(defview message-content [{:keys [audio audio-duration-ms message-id]}]
(letsubs [state (reagent/atom nil)
progress (reagent/atom 0)
@@ -1,34 +1,40 @@
(ns status-im.ui.screens.chat.message.message
(:require [re-frame.core :as re-frame]
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.models.delete-message-for-me]
[status-im.chat.models.images :as images]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.chat.models.reactions :as models.reactions]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[quo.design-system.colors :as colors]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer]
[status-im.ui.screens.chat.components.reply :as components.reply]
[status-im.ui.screens.chat.image.preview.views :as preview]
[status-im.ui.screens.chat.message.audio :as message.audio]
[status-im.chat.models.reactions :as models.reactions]
[status-im.ui.screens.chat.message.command :as message.command]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.chat.message.reactions :as reactions]
[status-im.ui.screens.chat.message.reactions-row :as reaction-row]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.utils.security :as security]
[status-im.ui.screens.chat.message.reactions :as reactions]
[status-im.ui.screens.chat.image.preview.views :as preview]
[quo.core :as quo]
[status-im.utils.config :as config]
[reagent.core :as reagent]
[status-im.ui.screens.chat.components.reply :as components.reply]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.chat.styles.photos :as photos.style]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.ui.components.animation :as animation]
[status-im.chat.models.images :as images]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer]
[status-im.ui.screens.chat.message.reactions-row :as reaction-row])
[status-im.utils.handlers :refer [>evt]]
[status-im.utils.config :as config]
[status-im.utils.security :as security]
[quo2.foundations.typography :as typography]
[quo2.foundations.colors :as quo2.colors])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
@@ -89,9 +95,12 @@
timestamp-str]])))
(defview quoted-message
[_ reply]
[react/view {:style (style/quoted-message-container)}
[components.reply/reply-message reply false]])
[_ reply pin?]
[react/view {:style (when-not pin? (style/quoted-message-container))}
[components.reply/reply-message reply false pin?]])
(defn system-text? [content-type]
(= content-type constants/content-type-system-text))
(defn render-inline [message-text content-type acc {:keys [type literal destination]}]
(case type
@@ -130,13 +139,14 @@
destination])
"mention"
(conj acc [react/text-class
{:style {:color (cond
(= content-type constants/content-type-system-text) colors/black
:else colors/mention-incoming)}
:on-press (when-not (= content-type constants/content-type-system-text)
#(re-frame/dispatch [:chat.ui/show-profile literal]))}
[mention-element literal]])
(conj acc
[react/view {:style {:background-color quo2.colors/primary-50-opa-10 :border-radius 6 :padding-horizontal 3}}
[react/text-class
{:style (merge {:color (if (system-text? content-type) colors/black quo2.colors/primary-50)}
(if (system-text? content-type) typography/font-regular typography/font-medium))
:on-press (when-not (system-text? content-type)
#(>evt [:chat.ui/show-profile literal]))}
[mention-element literal]]])
"status-tag"
(conj acc [react/text-class
{:style {:color colors/blue
@@ -219,28 +229,22 @@
(let [user-contact @(re-frame/subscribe [:multiaccount/contact])
contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])]
;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
(def pin-icon-width 9)
(def pin-icon-width 10)
(def pin-icon-height 15)
(defn pinned-by-indicator [display-photo? pinned-by]
[react/view {:style (style/pin-indicator display-photo?)
(defn pin-icon []
[icons/icon :main-icons/pin16 {:color (:text-04 @colors/theme)
:height pin-icon-height
:width pin-icon-width}])
(defn pinned-by-indicator [pinned-by]
[react/view {:style (style/pin-indicator)
:accessibility-label :pinned-by}
[react/view {:style (style/pinned-by-text-icon-container)}
[react/view {:style (style/pin-icon-container)}
[icons/icon :main-icons/pin {:color colors/gray
:height pin-icon-height
:width pin-icon-width
:background-color :red}]]
[quo/text {:weight :regular
:size :small
:color :main
:style (style/pinned-by-text)}
(i18n/label :t/pinned-by)]]
[quo/text {:weight :medium
:size :small
[pin-icon]
[quo/text {:size :small
:color :main
:style (style/pin-author-text)}
(pin-author-name pinned-by)]])
@@ -294,7 +298,8 @@
"Author, userpic and delivery wrapper"
[{:keys [last-in-group?
identicon
from in-popover? timestamp-str]
from in-popover? timestamp-str
deleted-for-me? pinned]
:as message} content {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[react/view {:style (style/message-wrapper message)
@@ -305,12 +310,12 @@
[react/view {:style (style/message-body)
:pointer-events :box-none}
[react/view (style/message-author-userpic)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group?)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])]
[react/view {:style (style/message-author-wrapper)}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group?)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/view {:style {:flex-direction :row :align-items :center}}
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
@@ -320,14 +325,19 @@
[react/text
{:style (merge
{:padding-left 5
:margin-top 2}
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]])
;;MESSAGE CONTENT
content
;; MESSAGE CONTENT
;; TODO(yqrashawn): wait for system message component to display deleted for me UI
(if deleted-for-me?
[react/view {:style {:border-width 2
:border-color :red}}
content]
content)
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
; delivery status
; delivery status
[react/view (style/delivery-status)
[message-delivery-status message]]]))
@@ -436,6 +446,13 @@
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :main-icons/pin-context20
:id (if pinned :unpin :pin)}])
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
@@ -474,6 +491,34 @@
[collapsible-text-message message on-long-press modal ref]
reaction-picker])
(defmethod ->message constants/content-type-pin [{:keys [from in-popover? timestamp-str] :as message} {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[react/view {:style (merge {:flex-direction :row :margin-vertical 8} (style/message-wrapper message))}
[react/view {:style {:width photos.style/default-size
:height photos.style/default-size
:margin-horizontal 8
:border-radius photos.style/default-size
:justify-content :center
:align-items :center
:background-color quo2.colors/primary-50-opa-10}}
[pin-icon]]
[react/view
[react/view {:style {:flex-direction :row :align-items :center}}
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[react/text {:style {:font-size 13}} (str " " (i18n/label :pinned-a-message))]
[react/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]]
[quoted-message response-to (:quoted-message message) true]]]))
(defmethod ->message constants/content-type-community
[message]
[community-content message])
@@ -581,6 +626,13 @@
:id :share
:icon :main-icons/share-context20
:label (i18n/label :t/share-image)}]
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
@@ -611,6 +663,13 @@
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :main-icons/pin-context20
:id (if pinned :unpin :pin)}
{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}
(when (and outgoing config/delete-message-enabled?)
{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
@@ -678,7 +737,7 @@
[message-content-wrapper message
[unknown-content-type message]])
(defn chat-message [{:keys [display-photo? pinned pinned-by] :as message}]
(defn chat-message [{:keys [pinned pinned-by mentioned] :as message}]
(let [reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
own-reactions (reduce (fn [acc {:keys [emoji-id own]}]
(if own (conj acc emoji-id) acc))
@@ -705,11 +764,12 @@
(into #{} (js->clj own-reactions))
#(on-emoji-press %))}]))
on-long-press (atom nil)]
[:<>
[react/view {:style (merge (when (or mentioned pinned) {:background-color quo2.colors/primary-50-opa-5 :border-radius 16 :margin-bottom 4}) {:margin-horizontal 8})}
(when pinned
[react/view {:style (style/pin-indicator-container)}
[pinned-by-indicator pinned-by]])
[->message message {:ref on-long-press
:modal false
:on-long-press on-open-drawer}]
[reaction-row/message-reactions message reactions nil on-emoji-press on-long-press] ;; TODO: pass on-open-drawer function
(when pinned
[react/view {:style (style/pin-indicator-container)}
[pinned-by-indicator display-photo? pinned-by]])]))
]))
@@ -92,6 +92,7 @@
(defn reactions-row [timeline margin-top]
{:flex-direction :row
:padding-right 8
:padding-bottom 8
:justify-content :flex-start
:margin-top margin-top
:flex-wrap :wrap
@@ -3,7 +3,8 @@
[quo.design-system.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.photos :as photos]
[quo2.foundations.colors :as quo2.colors]))
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.typography :as typography]))
(defn style-message-text
[]
@@ -71,30 +72,15 @@
{:align-self :flex-start
:padding-left 8})
(defn pin-indicator [display-photo?]
(merge
{:flex-direction :row
:border-top-left-radius 4
:border-top-right-radius 12
:border-bottom-left-radius 12
:border-bottom-right-radius 12
:padding-left 8
:padding-right 10
:padding-vertical 5
:background-color colors/gray-lighter
:justify-content :center
:max-width "80%"
:align-self :flex-start
:align-items :flex-start}
(when display-photo?
{:margin-left 44})))
(defn pin-indicator []
(merge {:flex-direction :row}))
(defn pin-indicator-container []
{:margin-top 2
{:margin-top 8
:margin-left 68
:justify-content :center
:align-self :flex-start
:align-items :flex-start
:padding-left 8})
:align-items :flex-start})
(defn pinned-by-text-icon-container []
{:flex-direction :row
@@ -108,14 +94,9 @@
:margin-top 1})
(defn pin-author-text []
{:margin-left 2
:margin-right 12
:padding-right 0
:left 12
:flex-direction :row
:flex-shrink 1
:align-self :flex-start
:overflow :hidden})
(merge typography/font-medium
{:color quo2.colors/primary-50
:bottom 2}))
(defn pinned-by-text []
{:margin-left 5})
@@ -127,7 +108,7 @@
(defn message-author-userpic []
(merge
{:width (+ 16 photos/default-size)} ;; 16 is for the padding
{:padding-left 8
{:padding-left 0
:padding-right 8}))
(def delivery-text
@@ -171,20 +152,17 @@
:flex-direction :row-reverse})
(defn message-view
[{:keys [content-type mentioned pinned]}]
[{:keys [content-type mentioned]}]
(merge
{:border-radius 10}
(cond
pinned {:background-color colors/pin-background}
(= content-type constants/content-type-system-text) nil
mentioned {:background-color colors/mentioned-background
:border-color colors/mentioned-border
:border-width 1}
(= content-type constants/content-type-audio) {:background-color colors/blue
:padding-horizontal 12
:padding-top 6}
:else {:background-color colors/white})
:padding-top 6})
(when (= content-type constants/content-type-emoji)
{:flex-direction :row})))
+71 -35
View File
@@ -419,10 +419,42 @@
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
;;TODO https://github.com/facebook/react-native/issues/30034
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]))
(def show-floating-scroll-down-button (reagent/atom false))
(def threshold-percentage-to-show-floating-scroll-down-button 75)
(defn scroll-to-bottom []
(some-> ^js @messages-list-ref (.scrollToOffset #js {:y 0 :animated true})))
(defn floating-scroll-down-button [show-input?]
[rn/touchable-without-feedback
{:on-press scroll-to-bottom}
[rn/view {:style {:position :absolute
:bottom (if show-input? 126 12)
:right 12
:height 24
:width 24
:align-items :center
:justify-content :center
:border-radius (/ 24 2)
:background-color (quo2.colors/theme-colors quo2.colors/neutral-80-opa-70 quo2.colors/white-opa-70)}}
[icons/icon
:main-icons/arrow-down {:color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-100)
:width 12
:height 12}]]])
(defn on-scroll [^js ev]
(let [y (-> ev .-nativeEvent .-contentOffset .-y)
layout-height (-> ev .-nativeEvent .-layoutMeasurement .-height)
threshold-height (* (/ layout-height 100) threshold-percentage-to-show-floating-scroll-down-button)
reached-threshold? (> y threshold-height)]
(when (not= reached-threshold? @show-floating-scroll-down-button)
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
(reset! show-floating-scroll-down-button reached-threshold?))))
(defn messages-view [{:keys [chat
bottom-space
pan-responder
@@ -438,39 +470,43 @@
mutual-contact-requests-enabled?
one-to-one?
(not contact-added?))]
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data (when-not should-send-contact-request?
messages)
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
:render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed
:on-end-reached list-on-end-reached
:on-scroll-to-index-failed identity ;;don't remove this
:content-container-style {:padding-top (+ bottom-space 16)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]))
[:<>
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data (when-not should-send-contact-request?
messages)
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
:render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed
:on-end-reached list-on-end-reached
:on-scroll-to-index-failed identity ;;don't remove this
:content-container-style {:padding-top (+ bottom-space 16)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
:scrollEventThrottle 16
:on-scroll on-scroll
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]
(when @show-floating-scroll-down-button
[floating-scroll-down-button show-input?])]))
(defn back-button []
[quo2.button/button {:type :grey
@@ -566,7 +602,7 @@
[accessory/view {:y position-y
:on-update-inset on-update}
[invitation-bar chat-id]])
[components/autocomplete-mentions text-input-ref max-bottom-space]
[components/autocomplete-mentions-old text-input-ref max-bottom-space]
(when show-input?
;; NOTE: this only accepts two children
[accessory/view {:y position-y
@@ -28,14 +28,21 @@
(def sort-list-by (reagent/atom :name))
(def mock-community-item-data ;; TODO: remove once communities are loaded with this data.
{:data {:status :gated
:locked true
;; :images {:thumbnail {:uri []}} ;; TODO address issue with context tag image
:cover (resources/get-image :community-cover)
:tokens [{:id 1 :group [{:id 1 :token-icon (resources/get-image :status-logo)}]}]
:tags [{:id 1 :tag-label (i18n/label :t/music) :resource (resources/get-image :music)}
{:id 2 :tag-label (i18n/label :t/lifestyle) :resource (resources/get-image :lifestyle)}
{:id 3 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]}})
{:data {:status :gated
:locked true
:cover (resources/get-image :community-cover)
:tokens [{:id 1
:group [{:id 1
:token-icon (resources/get-image :status-logo)}]}]
:tags [{:id 1
:tag-label (i18n/label :t/music)
:resource (resources/get-image :music)}
{:id 2
:tag-label (i18n/label :t/lifestyle)
:resource (resources/get-image :lifestyle)}
{:id 3
:tag-label (i18n/label :t/podcasts)
:resource (resources/get-image :podcasts)}]}})
(defn plus-button []
(let [logging-in? (<sub [:multiaccounts/login])]
@@ -179,7 +186,7 @@
(let [filters [{:id 1 :tag-label (i18n/label :t/music) :resource (resources/get-image :music)}
{:id 2 :tag-label (i18n/label :t/lifestyle) :resource (resources/get-image :lifestyle)}
{:id 3 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}
{:id 3 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]]
{:id 4 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]]
[react/scroll-view {:horizontal true
:height 48
:shows-horizontal-scroll-indicator false
@@ -1,72 +1,140 @@
(ns status-im.ui.screens.communities.community-overview-redesign
(:require [status-im.i18n.i18n :as i18n]
[status-im.ui.components.react :as react]
[quo2.components.markdown.text :as quo2.text]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button]
[quo2.components.list-items.preview-list :as preview-list]
[quo2.components.list-items.channel :as channel]
[quo2.components.dividers.divider-label :as divider-label]
[quo2.components.community.community-view :as community-view]
[quo2.components.tags.status-tags :as status-tags]
[status-im.ui.screens.communities.request-to-join-bottom-sheet-redesign :as request-to-join]
[status-im.ui.components.list.views :as list]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.screens.communities.styles :as styles]
[quo2.foundations.colors :as colors]
[status-im.ui.screens.communities.icon :as communities.icon]))
(defn preview-list-component []
[react/view {:style {:height 20 :margin-top 20}}
[quo2.text/text {:accessibility-label :communities-screen-title
:weight :semi-bold
:size :label}
"placeholder for preview list component"]])
;; Mocked list items
(def user-list
[{:full-name "Alicia K"}
{:full-name "Marcus C"}
{:full-name "MNO PQR"}
{:full-name "STU VWX"}])
(defn preview-user-list []
[react/view {:style {:flex-direction :row
:align-items :center
:margin-top 20}}
[preview-list/preview-list {:type :user
:user user-list :list-size 4 :size 24}]
[text/text {:accessibility-label :communities-screen-title
:style {:margin-left 8}
:size :label}
"Join Alicia, Marcus and 2 more"]]) ;; TODO remove mocked data and use from contacts list/communities members
(def list-of-channels {:Welcome [{:name "welcome"
:emoji "🤝"}
{:name "onboarding"
:emoji "🍑"}
{:name "intro"
:emoji "🦄"}]
:General [{:name "general"
:emoji "🐷"}
{:name "people-ops"
:emoji "🌏"}
{:name "announcements"
:emoji "🎺"}]
:Mobile [{:name "mobile"
:emoji "👽"}]})
(defn channel-list-component []
[react/view {:style {:height 200 :margin-top 20}}
[quo2.text/text {:accessibility-label :communities-screen-title
:weight :semi-bold
:size :label}
"placeholder for channel list component"]])
[react/scroll-view {:style {:margin-top 20}}
[:<> {:style {:flex 1}}
(map (fn [category]
^{:key (get category 0)}
[react/view {:flex 1}
[divider-label/divider-label
{:label (first category)
:chevron-position :left}]
[react/view
{:margin-left 8
:margin-top 10
:margin-bottom 8}
[list/flat-list
{:shows-horizontal-scroll-indicator false
:separator [react/view {:margin-top 4}]
:data ((first category) list-of-channels)
:render-fn channel/list-item}]]])
list-of-channels)]])
(defn community-card-page-view [{:keys [name description locked joined
status tokens cover tags] :as community}]
[react/view
{:style
{:height "100%"
{:flex 1
:border-radius 20}}
[react/view (styles/community-cover-container 148)
[react/view (styles/community-cover-container 148)
[react/image
{:source cover
:style {:height 180
:style {:position :relative
:flex 1}}]]
[react/view (styles/card-view-content-container 20)
[react/view (styles/card-view-chat-icon 80)
[communities.icon/community-icon-redesign community 80]]
(when (= status :gated)
[react/view (styles/permission-tag-styles)
[community-view/permission-tag-container
{:locked locked
:status status
:tokens tokens}]])
[community-view/community-title
{:title name
:size :large
:description description}]
[community-view/community-stats-column :card-view]
[community-view/community-tags tags]
[preview-list-component]
(when (not joined)
[quo2.button/button
{:on-press #(>evt [:bottom-sheet/show-sheet
{:content (fn [] [request-to-join/request-to-join community])
:content-height 300}])
:style
{:width "100%"
:margin-top 20
:margin-left :auto
:margin-right :auto}
:before :main-icons/community}
(i18n/label :join-open-community)])
[react/view {:flex 1
:height 20
:border-radius 16
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[react/view {:padding-horizontal 20}
[react/view {:border-radius 40
:border-width 1
:border-color colors/white
:position :absolute
:top (- (/ 80 2))
:left (/ 70 4)
:padding 2
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[communities.icon/community-icon-redesign community 80]]
(when (and (not joined)
(= status :gated))
[react/view (styles/permission-tag-styles)
[community-view/permission-tag-container
{:locked locked
:status status
:tokens tokens}]])
(when joined
[react/view {:position :absolute
:top 12
:right 12}
[status-tags/status-tag {:status {:type :positive} :label (i18n/label :joined)}]])
[community-view/community-title
{:title name
:size :large
:description description}]
[community-view/community-stats-column :card-view]
[community-view/community-tags tags]
[preview-user-list]
(when (not joined)
[button/button
{:on-press #(>evt [:bottom-sheet/show-sheet
{:content (constantly [request-to-join/request-to-join community])
:content-height 300}])
:style
{:width "100%"
:margin-top 20
:margin-left :auto
:margin-right :auto}
:before :main-icons2/communities}
(i18n/label :join-open-community)])]
[channel-list-component]]])
(defn overview []
(let [community (<sub [:get-screen-params :community-overview])]
[react/view {:style {}}
(let [community-mock (<sub [:get-screen-params :community-overview]) ;;TODO stop using mock data and only pass community id
community (<sub [:communities/community (:id community-mock)])]
[react/view {:style
{:height "100%"}}
[community-card-page-view
community]]))
(merge community-mock {:joined (:joined community)})]]))
@@ -1,16 +1,17 @@
(ns status-im.ui.screens.communities.request-to-join-bottom-sheet-redesign
(:require [status-im.i18n.i18n :as i18n]
[quo.core :as quo]
[reagent.core :as reagent]
[status-im.react-native.resources :as resources]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[quo2.components.markdown.text :as quo2.text]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.icon :as quo2.icon]
[quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button]
[quo2.components.selectors.disclaimer :as disclaimer]
[quo2.components.icon :as icon]
[status-im.utils.handlers :refer [>evt]]
;; [quo2.components.tags.context-tags :as quo2.context-tags]
[quo2.components.tags.context-tags :as context-tags]
[status-im.communities.core :as communities]
[quo2.foundations.colors :as quo2.colors]))
[quo2.foundations.colors :as colors]))
;; TODO: update with real data
(def community-rules [{:index 1
@@ -46,84 +47,75 @@
:width 18
:margin-left 1
:margin-right 9
:background-color quo2.colors/white
:border-color quo2.colors/neutral-20
:background-color colors/white
:border-color colors/neutral-20
:border-width 1
:border-radius 6}}
[quo2.text/text {:style
{:margin-left :auto
:margin-right :auto
:margin-top :auto
:margin-bottom :auto}
:accessibility-label :communities-rule-index
:weight :medium
:size :label}
[text/text {:style
{:margin-left :auto
:margin-right :auto
:margin-top :auto
:margin-bottom :auto}
:accessibility-label :communities-rule-index
:weight :medium
:size :label}
(str index)]]
[quo2.text/text
[text/text
{:accessibility-label :communities-rule-title
:weight :semi-bold
:size :paragraph-2}
title]]
[quo2.text/text
{:style {:margin-left 28}
[text/text
{:style {:margin-left 28 :margin-top 1}
:accessibility-label :communities-rule-content
:size :paragraph-2}
content]])
(defn community-rules-list [rules]
[list/flat-list
{:shows-horizontal-scroll-indicator false
:data rules
:separator [react/view {:margin-top 1}]
:render-fn community-rule-item}])
(defn rules-checkbox [agreed-to-rules?]
[react/view {:style
{:flex 1
:flex-direction :row
:margin-top 20
:background-color quo2.colors/neutral-20
:padding 12
:border-radius 12
:border-width 1
:border-color quo2.colors/neutral-30}}
[quo/checkbox {:value @agreed-to-rules?
:on-change #(swap! agreed-to-rules? not)}]
[quo2.text/text {:accessibility-label :communities-accept-rules
:size :paragraph-2
:style {:margin-left 8}}
(i18n/label :t/accept-community-rules)]])
(defn request-to-join [community]
(let [agreed-to-rules? (reagent/atom false)]
(fn []
[react/view {:style {:flex 1 :margin-left 20 :margin-right 20 :margin-bottom 20}}
[react/view {:style {:flex 1 :margin-left 20 :margin-right 20 :margin-bottom 20}}
[react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :space-between}}
[quo2.text/text {:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
[text/text {:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
(i18n/label :t/join-open-community)]
[react/view {:style {:height 32
:width 32
:align-items :center
:background-color quo2.colors/white
:border-color quo2.colors/neutral-20
:background-color colors/white
:border-color colors/neutral-20
:border-width 1
:border-radius 6
:border-radius 8
:display :flex
:justify-content :center}}
[quo2.icon/icon :main-icons2/info]]]
;; TODO address issue with context tag image
;; [quo2.context-tags/context-tag {:style {:margin-right :auto :margin-top 8}} (get-in community [:images :thumbnail :uri]) (:name community)]
[quo2.text/text {:style {:margin-top 24}
:accessibility-label :communities-rules-title
:weight :semi-bold
:size :paragraph-1}
[icon/icon :main-icons2/info]]]
;; TODO get tag image from community data
[context-tags/context-tag
{:style
{:margin-right :auto
:margin-top 8}}
(resources/get-image :status-logo) (:name community)]
[text/text {:style {:margin-top 24}
:accessibility-label :communities-rules-title
:weight :semi-bold
:size :paragraph-1}
(i18n/label :t/community-rules)]
[community-rules-list community-rules]
[rules-checkbox agreed-to-rules?]
[disclaimer/disclaimer
{:container-style {:margin-top 20}
:on-change #(swap! agreed-to-rules? not)}
(i18n/label :t/accept-community-rules)]
[react/view {:style {:width "100%"
:margin-top 32 :margin-bottom 16
@@ -131,8 +123,10 @@
:flex-direction :row
:align-items :center
:justify-content :space-evenly}}
[quo2.button/button {:on-press #(>evt [:bottom-sheet/hide])
:type :grey :style {:flex 1 :margin-right 12}} (i18n/label :t/cancel)]
[quo2.button/button
{:on-press #(>evt [::communities/join (:id community)])
:disabled (not agreed-to-rules?) :style {:flex 1}} (i18n/label :t/join-open-community)]]])))
[button/button {:on-press #(>evt [:bottom-sheet/hide])
:type :grey :style {:flex 1 :margin-right 12}} (i18n/label :t/cancel)]
[button/button
{:on-press (fn []
(>evt [::communities/join (:id community)])
(>evt [:bottom-sheet/hide]))
:disabled (not @agreed-to-rules?) :style {:flex 1}} (i18n/label :t/join-open-community)]]])))
+2 -20
View File
@@ -26,12 +26,10 @@
[status-im.utils.config :as config]
[quo2.components.markdown.text :as quo2.text]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.utils.datetime :as datetime]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.chat-icon.styles :as chat-icon.styles]
[quo2.foundations.colors :as quo2.colors]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.info.information-box :as information-box])
[quo2.components.buttons.button :as quo2.button])
(:require-macros [status-im.utils.views :as views]))
(defn home-tooltip-view []
@@ -189,12 +187,9 @@
[home-tooltip-view]
[react/view {:height 68}])}])))
(def ens-banner-expire-timestamp 1665010800)
(views/defview communities-and-chats-old []
(views/letsubs [{:keys [items search-filter]} [:home-items]
hide-home-tooltip? [:hide-home-tooltip?]
information-box-closed? [:information-box-closed? :ens-banner]]
hide-home-tooltip? [:hide-home-tooltip?]]
(if (and (empty? items)
(empty? search-filter)
hide-home-tooltip?
@@ -210,19 +205,6 @@
:header [:<>
(when (or (seq items) @search-active? (seq search-filter))
[search-input-wrapper-old search-filter (empty? items)])
(when (< (datetime/timestamp-sec) ens-banner-expire-timestamp)
[information-box/information-box
{:type :informative
:closable? true
:closed? information-box-closed?
:icon :main-icons/info
:style {:margin 20}
:button-label (i18n/label :t/open-dapp2)
:on-button-press #(re-frame/dispatch
[:browser.ui/open-url "https://ens-collect.status.im/"])
:id :ens-banner
:on-close #(re-frame/dispatch [:close-information-box :ens-banner true])}
(i18n/label :t/ens-banner-message)])
(when (and (empty? items)
(or @search-active? (seq search-filter)))
[start-suggestion search-filter])]
@@ -6,29 +6,17 @@
[quo.core :as quo]
[quo.design-system.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.utils.utils :as utils]
[status-im.ui.screens.wallet-connect.session-proposal.views :refer [app-management-sheet-view]]
[status-im.ui.components.bottom-panel.views :as bottom-panel]
[status-im.utils.handlers :refer [<sub]]
[reagent.core :as reagent]))
[status-im.utils.utils :as utils]))
(defn account-selector-bottom-sheet [{:keys [session show-account-selector? idx]}]
(when @show-account-selector?
[rn/view {:style (cond-> {:height 50}
(= idx 0) (assoc :margin-top 50))}
[bottom-panel/animated-bottom-panel
session
app-management-sheet-view
true]]))
(defn print-session-info [{:keys [session visible-accounts show-account-selector?]}]
(let [peer-meta (get-in session [:params 0 :peerMeta])
peer-id (get-in session [:params 0 :peerId])
name (get peer-meta :name)
url (get peer-meta :url)
account (get-in session [:params 0 :accounts 0])
icons (get peer-meta :icons)
icon-uri (first (status-im.utils.utils/exclude-svg-resources icons))
(defn print-session-info [session]
(let [peer-meta (get-in session [:params 0 :peerMeta])
peer-id (get-in session [:params 0 :peerId])
name (get peer-meta :name)
url (get peer-meta :url)
account (get-in session [:params 0 :accounts 0])
icons (get peer-meta :icons)
icon-uri (first (status-im.utils.utils/exclude-svg-resources icons))
visible-accounts @(re-frame/subscribe [:visible-accounts-without-watch-only])
selected-account (first (filter
#(= account
(:address %))
@@ -37,51 +25,28 @@
[rn/view
[:<>
[rn/view {:style styles/app-row}
[react/image {:style styles/dapp-icon
:source {:uri icon-uri}}]
[react/image {:style styles/dapp-icon :source {:uri icon-uri}}]
[rn/view {:style styles/app-column}
[quo/text {:style styles/dapp-name} name]
[quo/text {:style styles/dapp-url} url]]
[rn/view {:flex-direction :row
:position :absolute
:right 10
:align-items :center}
[rn/touchable-opacity {:style styles/delete-icon-container
:position :absolute
:right 10
:align-items :center}
[rn/touchable-opacity {:style styles/delete-icon-container
:on-press #(re-frame/dispatch [:wallet-connect-legacy/disconnect session])}
[icons/icon :icons/delete {:width 20
:height 20
[icons/icon :icons/delete {:width 20
:height 20
:container-style {:elevation 3}
:color colors/red}]]
:color colors/red}]]
(when selected-account ;; The account might not be available in theory, if deleted
[rn/touchable-opacity {:style (styles/selected-account-container (:color selected-account))
:on-press #(swap! show-account-selector? not)}
[rn/view {:style (styles/selected-account-container (:color selected-account))}
[rn/text {:style styles/selected-account} (:name selected-account)]])]]]]))
(defn list-item [{:keys [session visible-accounts show-account-selector?]} idx]
[rn/view
[print-session-info {:session session
:visible-accounts visible-accounts
:show-account-selector? show-account-selector?}]
[account-selector-bottom-sheet {:session session
:show-account-selector? show-account-selector?
:idx idx}]])
(defn list-comp [sessions visible-accounts]
(let [items (reagent/atom (doall (map (fn [session]
(let [show-account-selector? (reagent/atom false)]
{:visible-accounts visible-accounts
:show-account-selector? show-account-selector?
:session session}))
@sessions)))]
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:data @items
:render-fn list-item
:key-fn str}]))
(defn views []
(let [sessions (reagent/atom (<sub [:wallet-connect-legacy/sessions]))
visible-accounts (<sub [:visible-accounts-without-watch-only])]
[list-comp sessions visible-accounts]))
(let [legacy-sessions @(re-frame/subscribe [:wallet-connect-legacy/sessions])]
[rn/view {:margin-top 10}
(doall (map print-session-info legacy-sessions))]))
+2
View File
@@ -182,3 +182,5 @@
;; usable enough to replace the old one **in the new UI**.
(def new-activity-center-enabled?
(atom false))
(def delete-message-for-me-undo-time-limit-ms 4000)
+15
View File
@@ -0,0 +1,15 @@
(ns status-im.utils.number)
(defn naive-round
"Quickly and naively round number `n` up to `decimal-places`.
Example usage: use it to avoid re-renders caused by floating-point number
changes in Reagent atoms. Such numbers can be rounded up to a certain number
of `decimal-places` in order to avoid re-rendering due to tiny fractional
changes.
Don't use this function for arbitrary-precision arithmetic."
[n decimal-places]
(let [scale (Math/pow 10 decimal-places)]
(/ (Math/round (* n scale))
scale)))
-10
View File
@@ -1,10 +0,0 @@
(ns status-im.utils.profiler)
(defmacro with-measure
[name & body]
`(let [start# (js/performance.now)
res# (do ~@body)
end# (js/performance.now)
time# (.toFixed (- end# start#) 2)]
(taoensso.timbre/info "[perf|" ~name "] => " time#)
res#))

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