Compare commits

..
113 changed files with 1010 additions and 2904 deletions
-2
View File
@@ -36,8 +36,6 @@ 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.2
1.20.0
+4 -29
View File
@@ -14,25 +14,14 @@ 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` or `make run-ios` - Builds the Android/iOS 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` - Builds the Android 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.
## 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).
You need to have your emulator or real devices running and visible to adb, before you run `make run-android`.
# Build release
@@ -82,17 +71,3 @@ 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.644.0)
aws-sdk-core (3.159.0)
aws-partitions (1.622.0)
aws-sdk-core (3.136.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.2)
aws-sigv4 (1.5.1)
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.93.0)
excon (0.92.4)
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.210.1)
fastlane (2.209.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.29.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-core (0.9.0)
google-apis-androidpublisher_v3 (0.25.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-core (0.7.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.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-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-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.3.0)
google-cloud-storage (1.43.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.39.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-apis-storage_v1 (~> 0.17.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.20
2.3.9
+24 -24
View File
@@ -45,10 +45,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0x58gl654kcx65wyrix5swh5f9y431881a5mql8g3zwshd81fyb3";
sha256 = "1py4q91ll3v7ylcqflgd190y40d3ixgrhpln011gr5adgbzg9hr6";
type = "gem";
};
version = "1.644.0";
version = "1.622.0";
};
aws-sdk-core = {
dependencies = ["aws-eventstream" "aws-partitions" "aws-sigv4" "jmespath"];
@@ -56,10 +56,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0cdjmpblskjmhag7wx6scwkd3cw1gfh85syr599s05k8zp6y4qw8";
sha256 = "111zdfl6p1n949rvsfr0c88k9yzpibrcd8fihyshbibc2w06qj2b";
type = "gem";
};
version = "3.159.0";
version = "3.136.0";
};
aws-sdk-kms = {
dependencies = ["aws-sdk-core" "aws-sigv4"];
@@ -89,10 +89,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "11hkna2av47bl0yprgp8k4ya70rc3m2ib5w10fn0piplgkkmhz7m";
sha256 = "1d4bifmll4hrf4gihr5hdvn59wjpz4qpyg5jj95kp17fykzqg36n";
type = "gem";
};
version = "1.5.2";
version = "1.5.1";
};
babosa = {
groups = ["default"];
@@ -213,10 +213,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0b3rfqy87yiv9xmh260nyddxxjqj0vy32xvajvyn5jnjx96jwa24";
sha256 = "0cdc76kgr4f1mq4jwbmq1qvr9c15hb4r1cx4dvrdra13vy9sckb5";
type = "gem";
};
version = "0.93.0";
version = "0.92.4";
};
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 = "1wxzcs81c5ji30hrz64884rg0w56v2nwjyiyc8daka578bg7s8d6";
sha256 = "17m4nrpsjx2kadgy3qasis9d8rzajxgi9gzwkvdj7c0jjs6a768d";
type = "gem";
};
version = "2.210.1";
version = "2.209.1";
};
fastlane-plugin-clean_testflight_testers = {
groups = ["default"];
@@ -410,10 +410,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0z2i5wpkawkf4f42i55b1240iw8819wx828579s4vavhd9s17yik";
sha256 = "0psz3w8c95ashk5hlfvn5l32mg111z7fv07ngvvgm5mkw6wksh4d";
type = "gem";
};
version = "0.29.0";
version = "0.25.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 = "1w9m4zc5xswz2h7gj4jvnb1ivzb6lcsl75fnw8ip7qz6hzwfgrlc";
sha256 = "07jhk74awjc1npf2rim4kmwz4v0n3ny6y5g31saq7aqxkjmlp3hd";
type = "gem";
};
version = "0.9.0";
version = "0.7.0";
};
google-apis-iamcredentials_v1 = {
dependencies = ["google-apis-core"];
@@ -432,10 +432,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "06smnmn2s460xl9x9rh07a3fkqdrjjy6azmx8iywggqgv2k5d8p9";
sha256 = "0mf9l1r28hxqqh2ilza2pfffy32vvzq10ifqf88a732k16iwvin1";
type = "gem";
};
version = "0.15.0";
version = "0.13.0";
};
google-apis-playcustomapp_v1 = {
dependencies = ["google-apis-core"];
@@ -443,10 +443,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "00nfh3xh51mbh02f8nqlsijdh59k9vjs5sglvpv09pl3wnhirf7w";
sha256 = "0jgmfayhww7sy06pzf8r7bvxjix4jdazbyyy4mhp6ghv1s9r85w7";
type = "gem";
};
version = "0.11.0";
version = "0.10.0";
};
google-apis-storage_v1 = {
dependencies = ["google-apis-core"];
@@ -454,10 +454,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "17qamcjnf22zvw1g169g8a2gkzdsxx4ij3a4ganihyrcf9r62asj";
sha256 = "19ccrdgb34w49l62p28yy5qrgwgb4wxs9q3mnb2d334q4q3wsk9f";
type = "gem";
};
version = "0.19.0";
version = "0.17.0";
};
google-cloud-core = {
dependencies = ["google-cloud-env" "google-cloud-errors"];
@@ -486,10 +486,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0jynh1s93nl8njm5l5wcy86pnjmv112cq6m0443s52f04hg6h2s5";
sha256 = "0nakfswnck6grjpyhckzl40qccyys3sy999h5axk0rldx96fnivd";
type = "gem";
};
version = "1.3.0";
version = "1.2.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 = "0j3hqyb4zgj6az6p0bz0kgskl6fddrwb095kxfbdx8r11m07mfr8";
sha256 = "030y55cybc5w538bdd2xxjqwfka8fnkhv7ld4iqmn7byrkgy9dxc";
type = "gem";
};
version = "1.43.0";
version = "1.39.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.38):
- react-native-status-keycard (2.5.37):
- Keycard
- React
- react-native-webview (11.16.0):
@@ -640,7 +640,7 @@ SPEC CHECKSUMS:
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
glog: 36ce0530c6d2c3a5a4326885ef4069564887a1db
glog: 64a900d72fb14cd1b2cdf3b717a5555ceb889d49
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: f60ca57d789aad6875c64ece81ab06ef0609e0d3
react-native-status-keycard: 961d01ca190889ddf220206822fd752f8f4f3f7a
react-native-webview: 28a8636d97ee641f2ee8f20492d7a6c269c1d703
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
@@ -172,15 +172,12 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
NSURL *networkDir = [rootUrl URLByAppendingPathComponent:networkDirPath];
NSURL *originalGethLogsFile = [networkDir URLByAppendingPathComponent:@"geth.log"];
NSURL *gethLogsFile = [logsFolderName URLByAppendingPathComponent:@"mainnet_geth.log"];
NSURL *gethLogsFile = [logsFolderName URLByAppendingPathComponent:@"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];
@@ -188,7 +185,6 @@ 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];
@@ -426,10 +422,12 @@ 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:@"geth.log" forKey:@"LogFile"];
[configJSON setValue:logUrl.path forKey:@"LogFile"];
NSString *resultingConfig = [configJSON bv_jsonStringWithPrettyPrint:NO];
NSLog(@"node config %@", resultingConfig);
@@ -439,7 +437,7 @@ RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
withIntermediateDirectories:YES attributes:nil error:nil];
}
NSLog(@"logUrlPath %@ rootDir %@", @"geth.log", rootUrl.path);
NSLog(@"logUrlPath %@ rootDir %@", logUrl.path, rootUrl.path);
NSURL *absLogUrl = [absDataDirUrl URLByAppendingPathComponent:@"geth.log"];
if(![fileManager fileExistsAtPath:absLogUrl.path]) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
-3
View File
@@ -72,9 +72,6 @@ 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;
+3 -6
View File
@@ -4,10 +4,8 @@
let
inherit (lib) catAttrs unique;
# Sub-shells preparing various dependencies.
nodejs-sh = callPackage ./shells/nodejs.nix { };
bundler-sh = callPackage ./shells/bundler.nix { };
cocoapods-sh = callPackage ./shells/cocoapods.nix { };
cocoapods-sh = callPackage ./shells/pod.nix { };
status-go-sh = callPackage ./shells/status-go.nix { inherit status-go; };
in {
@@ -15,7 +13,7 @@ in {
shell = mkShell {
buildInputs = with pkgs; [
xcodeWrapper watchman procps
xcodeWrapper watchman bundler procps
flock # used in nix/scripts/node_modules.sh
];
@@ -23,8 +21,7 @@ in {
inputsFrom = [
fastlane.shell
cocoapods-sh
nodejs-sh # before 'pod install'
bundler-sh # before 'pod install'
nodejs-sh # before 'pod install'
status-go-sh # before 'pod install'
];
};
-8
View File
@@ -1,8 +0,0 @@
{ mkShell, bundler }:
mkShell {
buildInputs = [ bundler ];
shellHook = ''
bundle install --quiet --gemfile=fastlane/Gemfile
'';
}
+24
View File
@@ -0,0 +1,24 @@
{ 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
'';
}
+7 -8
View File
@@ -33,19 +33,18 @@ in {
gradlePropParser = callPackage ./tools/gradlePropParser.nix { };
# Package version adjustments
gradle = super.gradle_5;
nodejs = super.nodejs-16_x;
yarn = super.yarn.override { nodejs = super.nodejs-16_x; };
openjdk = super.openjdk8_headless;
gradle = super.pkgs.gradle_5;
nodejs = super.pkgs.nodejs-16_x;
openjdk = super.pkgs.openjdk8_headless;
xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } {
version = "13.3";
allowHigher = true;
};
go = super.go_1_18;
buildGoPackage = super.buildGo118Package;
buildGoModule = super.buildGo118Module;
go = super.pkgs.go_1_17;
buildGoPackage = super.pkgs.buildGo117Package;
buildGoModule = super.pkgs.buildGo117Module;
gomobile = (super.gomobile.overrideAttrs (old: {
patches = self.fetchurl { # https://github.com/golang/mobile/pull/84
patches = self.pkgs.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 = "579238da5f431b7833a9f0681663900aaf0dd1e8";
sha256 = "sha256-cDwASlAf/h0fsHtDm9yNBHEHK0uq6do+mIUEgh1i5yg=";
rev = "280d485b6f94d5ee8645aaf098c61a5def5df2be";
sha256 = "sha256-8ZWWJEDypkTCrzIWUxpin7CS2M3uPjjbl/8mpcVZQ9I";
# 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.2";
platformToolsVersion = "33.0.1";
buildToolsVersions = [ "31.0.0" ];
platformVersions = [ "31" ];
cmakeVersions = [ "3.18.1" ];
+4 -1
View File
@@ -27,11 +27,13 @@
"@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.2.0",
"eth-phishing-detect": "^1.1.13",
"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",
@@ -71,6 +73,7 @@
"react-native-touch-id": "^4.4.1",
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#refs/tags/v11.16.0-status",
"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.

Before

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

-3
View File
@@ -248,8 +248,6 @@
(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}})
@@ -310,7 +308,6 @@
"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
+3 -4
View File
@@ -9,10 +9,9 @@
:small 20})
(defn icon-avatar
[{:keys [size icon color opacity]
:or {opacity 20}}]
[{:keys [size icon color]}]
(let [component-size (size sizes)
circle-color (colors/custom-color color 50 opacity)
circle-color (colors/custom-color color 50 20)
icon-color (colors/custom-color-by-theme color 50 60)
icon-size (case size
:big 20
@@ -26,4 +25,4 @@
:align-items :center}}
[icons/icon icon {:container-style {:width icon-size
:height icon-size}
:color icon-color}]]))
:color icon-color}]]))
+36 -34
View File
@@ -4,7 +4,8 @@
[quo2.foundations.colors :as colors]
[quo2.components.icon :as icons]
[clojure.string :refer [upper-case split blank?]]
[quo.theme :refer [dark?]]))
[quo.theme :refer [dark?]]
[status-im.ui.components.react :as react]))
(def sizes {:big {:outer 80
:inner 72
@@ -97,46 +98,47 @@
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)
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)
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
: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 profile-picture
;; display image
[rn/image {:style (container-styling inner-dimensions outer-dimensions)
:source profile-picture}]
;; else display initials
(if using-profile-picture?
[react/image {:style (container-styling inner-dimensions outer-dimensions)
:source {:uri profile-picture}}]
[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?)]]))
@@ -1,99 +0,0 @@
(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])]])))
@@ -1,68 +0,0 @@
(ns quo2.components.drawers.action-drawers
(:require [status-im.ui.components.react :as react]
[quo.react-native :as rn]
[quo2.components.markdown.text :as text]
[quo2.components.icon :as icon]
[quo2.foundations.colors :as colors]))
(defn- get-icon-color [section]
(if (= section :bottom)
(colors/theme-colors colors/danger-50 colors/danger-60)
(colors/theme-colors colors/neutral-50 colors/neutral-40)))
(defn action [section]
(fn [{:keys [icon label sub-label right-icon on-press]}]
[rn/touchable-opacity {:on-press on-press}
[react/view {:style
{:flex 1
:height (if sub-label 56 47)
:margin-horizontal 20
:flex-direction :row}}
[react/view {:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:margin-right 12
:width 20}}
[icon/icon icon
{:color (get-icon-color section)
:size 20}]]
[react/view
{:style
{:flex 1
:justify-content :center}}
[text/text
{:size :paragraph-1
:weight :medium
:style
{:color (when (= section :bottom)
(colors/theme-colors colors/danger-50 colors/danger-60))}}
label]
(when sub-label [text/text
{:size :paragraph-2
:style
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
sub-label])]
(when right-icon
[react/view {:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20}}
[icon/icon right-icon
{:color (get-icon-color section)
:size 20}]])]]))
(defn action-drawer [{:keys [actions actions-with-consequence]}]
[:<> {:style
{:flex 1}}
(map (action :top) actions)
(when actions-with-consequence
[:<>
[rn/view {:style {:border-top-width 1
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row}}]
(map (action :bottom) actions-with-consequence)])])
@@ -1,162 +0,0 @@
(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]]))])
@@ -1,35 +0,0 @@
(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}]]]])
@@ -1,175 +0,0 @@
(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))]]))
+11 -132
View File
@@ -1,142 +1,21 @@
(ns quo2.components.tabs.tabs
(:require [oops.core :refer [oget]]
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[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)
[quo2.components.tabs.tab :as tab]))
(defn tabs [{:keys [default-active on-change]}]
(let [active-tab-id (reagent/atom default-active)]
(fn [{:keys [data size] :or {size default-tab-size}}]
(fn [{:keys [data size] :or {size 32}}]
(let [active-id @active-tab-id]
[rn/view {:flex-direction :row}
(for [{:keys [label id]} data]
^{:key id}
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
[rn/view {:margin-right (if (= size 32) 12 8)}
[tab/tab
{: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]])})])))))
{:id id
:size size
:active (= id active-id)
:on-press #(do (reset! active-tab-id %)
(when on-change
(on-change %)))}
label]])]))))
+21 -20
View File
@@ -3,7 +3,8 @@
[quo.react-native :as rn]
[quo.theme :as theme]
[quo2.components.markdown.text :as text]
[status-im.ui.components.icons.icons :as icons]))
[status-im.ui.components.icons.icons :as icons]
[quo2.components.tags.tag :as tag]))
(def themes {:light {:background-color colors/neutral-20}
:dark {:background-color colors/neutral-80}})
@@ -76,23 +77,23 @@
:or
{size :small border-color (colors/custom-color-by-theme :purple 50 60)}}]
[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/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)]))
+1 -7
View File
@@ -119,7 +119,6 @@
;;;; 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}
@@ -146,11 +145,6 @@
: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/...
@@ -161,7 +155,7 @@
([color suffix]
(custom-color color suffix nil))
([color suffix opacity]
(let [base-color (get-in colors-map [(keyword color) suffix])]
(let [base-color (get-in customization [(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/wallet
{:key :main-icons/walelt
:value "Wallet"}
{:key :main-icons/play
:value "Play"}]}
+4 -11
View File
@@ -3,8 +3,7 @@
[quo2.foundations.colors :as colors]
[quo.previews.preview :as preview]
[quo2.components.avatars.user-avatar :as quo2]
[reagent.core :as reagent]
[status-im.react-native.resources :as resources]))
[reagent.core :as reagent]))
(def descriptor [{:label "Size:"
:key :size
@@ -33,15 +32,9 @@
{:label "Full name separated by space"
:key :full-name
: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)}]}])
{:label "Profile Picture URL"
:key :profile-picture
:type :text}])
(defn cool-preview []
(let [state (reagent/atom {:full-name "A Y"
@@ -1,45 +0,0 @@
(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}]])
@@ -1,59 +0,0 @@
(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))}]])
+3 -23
View File
@@ -13,12 +13,10 @@
[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]
@@ -27,7 +25,6 @@
[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]
@@ -35,7 +32,6 @@
[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]
@@ -47,7 +43,6 @@
[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
@@ -68,10 +63,7 @@
:component channel-avatar/preview-channel-avatar}]
:buttons [{:name :button
:insets {:top false}
:component button/preview-button}
{:name :dynamic-button
:insets {:top false}
:component dynamic-button/preview-dynamic-button}]
:component button/preview-button}]
:community [{:name :community-cards
:insets {:top false}
:component community-card/preview-community-card}]
@@ -84,9 +76,6 @@
{: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}]
@@ -107,22 +96,13 @@
:component text/preview-text}]
:messages [{:name :gap
:insets {:top false}
:component messages-gap/preview-messages-gap}
{:name :system-messages
:insets {:top false}
:component system-message/preview-system-message}]
:component messages-gap/preview-messages-gap}]
: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}
{: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}]
:component top-nav/preview-top-nav}]
:notifications [{:name :activity-logs
:insets {:top false}
:component activity-logs/preview-activity-logs}]
@@ -1,68 +0,0 @@
(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}]])
@@ -1,59 +0,0 @@
(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
@@ -1,106 +0,0 @@
(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}]])
+2 -1
View File
@@ -14,7 +14,8 @@
[rn/view {:padding-vertical 60
:align-items :center}
[quo2/disclaimer
{:container-style {:margin-bottom 40}
{:checked? @checked?
:container-style {:margin-bottom 40}
:on-change #(swap! checked? not)}
"I agree with the community rules"]
[button/button
@@ -145,7 +145,6 @@
[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]
+56 -84
View File
@@ -1,6 +1,5 @@
(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]
@@ -9,117 +8,90 @@
;;;; Notification reconciliation
(defn- update-notifications
"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))
[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))))
(fx/defn notifications-reconcile
{:events [:activity-center.notifications/reconcile]}
[{:keys [db]} new-notifications]
(when (seq new-notifications)
{:db (update-in db [:activity-center :notifications]
update-notifications 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)))}))
;;;; Notifications fetching and pagination
(def defaults
{:filter-status :unread
:filter-type constants/activity-center-notification-type-no-type
:notifications-per-page 10})
(def notifications-per-page
20)
(def start-or-end-cursor
"")
(defn- valid-cursor?
[cursor]
(and (some? cursor)
(not= cursor start-or-end-cursor)))
(defn- filter-status->rpc-method
[filter-status]
(if (= filter-status :read)
(defn notifications-group->rpc-method
[notifications-group]
(if (= notifications-group :notifications-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]} {: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 %])}]}))
[{: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 %])}]}))
(fx/defn notifications-fetch-first-page
{:events [:activity-center.notifications/fetch-first-page]}
[{: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)))]
[{:keys [db] :as cofx} {:keys [status-filter] :or {status-filter :unread}}]
(let [notifications-group (notifications-read-status->group status-filter)]
(fx/merge cofx
{:db (-> db
(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}))))
(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))))
(fx/defn notifications-fetch-next-page
{:events [:activity-center.notifications/fetch-next-page]}
[{:keys [db] :as cofx}]
(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}))))
(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))))
(fx/defn notifications-fetch-success
{:events [:activity-center.notifications/fetch-success]}
[{: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))))}))
[{: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)))})
(fx/defn notifications-fetch-error
{:events [:activity-center.notifications/fetch-error]}
[{:keys [db]} filter-type filter-status error]
[{:keys [db]} notifications-group error]
(log/warn "Failed to load Activity Center notifications" error)
{:db (update-in db [:activity-center :notifications filter-type filter-status] dissoc :loading?)})
{:db (update-in db [:activity-center notifications-group] dissoc :loading?)})
+68 -283
View File
@@ -2,285 +2,99 @@
(: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.events
[status-im.test-helpers :as h]))
[status-im.test-helpers :as h]
status-im.events))
(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 [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])
(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])
(rf/dispatch [:activity-center.notifications/reconcile nil])
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
(is (= read (get-in (h/db) [:activity-center :notifications-read :data])))
(is (= unread (get-in (h/db) [:activity-center :notifications-unread :data]))))))
(testing "removes dismissed or accepted notifications"
(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}
{: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 [: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 [:activity-center.notifications/reconcile
[{: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}]])
[{:id "0x1" :read true :dismissed true}
{:id "0x3" :read true :accepted true}
{:id "0x4" :read false :dismissed true}
{:id "0x5" :read false :accepted true}]])
(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])))))
(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])))))
(testing "replaces old notifications with newly arrived ones"
(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}]}}
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 [: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 [:activity-center.notifications/reconcile
[{: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}]])
[{:id "0x1" :read true :name "ABC"}
{:id "0x3" :read false :name "XYZ"}]])
(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]))))))
(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]))))))
(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 [{:id "0x1"
:type c/activity-center-notification-type-one-to-one-chat
:read false
:chatId "0x9"}]}))
(h/stub-fx-with-callbacks ::json-rpc/call
:on-success (constantly {:cursor "10"
:notifications [{:chatId "0x1"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-type c/activity-center-notification-type-one-to-one-chat}])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(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}))))))
(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]))))))))
(testing "does not fetch next page when pagination cursor reached the end"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
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 [:test/assoc-in [:activity-center :current-status-filter] :unread])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@@ -290,54 +104,30 @@
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:id "0x1"
:type c/activity-center-notification-type-mention
:read false
:chatId "0x9"}]}))
(h/stub-fx-with-callbacks ::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:chatId "0x1"}]}))
(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-mention])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-mention :unread :cursor]
"10"])
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-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]))
"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}))))))
(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]))))))))
(testing "does not fetch next page while it is still loading"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
: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 [: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 [:activity-center.notifications/fetch-next-page])
@@ -350,18 +140,13 @@
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
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 [:test/assoc-in [:activity-center :current-status-filter] :unread])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(is (nil? (get-in (h/db) [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :loading?])))
(is (nil? (get-in (h/db) [:activity-center :notifications-unread :loading?])))
(is (= [:activity-center.notifications/fetch-error
c/activity-center-notification-type-one-to-one-chat
:unread
:notifications-unread
:fake-error]
(:args (last @spy-queue))))))))
+17 -27
View File
@@ -22,9 +22,7 @@
[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.browser.eip3085 :as eip3085]
[status-im.browser.eip3326 :as eip3326]))
[status-im.utils.debounce :as debounce]))
(fx/defn update-browser-option
[{:keys [db]} option-key option-value]
@@ -347,7 +345,7 @@
constants/web3-eth-sign constants/web3-keycard-sign-typed-data} method))
(fx/defn web3-send-async
[cofx dapp-name {:keys [method params id] :as payload} message-id]
[cofx {: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))]
@@ -373,33 +371,25 @@
(dissoc :gasPrice))})
{:on-result [:browser.dapp/transaction-on-result message-id id]
:on-error [:browser.dapp/transaction-on-error message-id]}))))
(cond
(#{"eth_accounts" "eth_coinbase"} method)
(if (#{"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])}})
(= 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}])]}))))
(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}])]})))))
(fx/defn handle-no-permissions [cofx {:keys [method id]} message-id]
(if (= method "eth_accounts")
@@ -429,7 +419,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 dapp-name payload message-id)))
(web3-send-async cofx payload message-id)))
(fx/defn handle-scanned-qr-code
{:events [:browser.bridge.callback/qr-code-scanned]}
-69
View File
@@ -1,69 +0,0 @@
;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
@@ -1,43 +0,0 @@
;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.")}}})))
@@ -1,68 +0,0 @@
(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
%])}]}))
@@ -1,115 +0,0 @@
(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)))))
+6 -7
View File
@@ -185,7 +185,7 @@
[alias name nickname]))
(defn add-searchable-phrases-to-contact
[{:keys [alias name added? blocked? identicon public-key nickname ens-verified]} community-chat?]
[{:keys [alias name added? blocked? identicon public-key nickname]} community-chat?]
(when (and alias
(not (string/blank? alias))
(or name
@@ -194,12 +194,11 @@
community-chat?)
(not blocked?))
(add-searchable-phrases
{: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})))
{:alias alias
:name (or (utils/safe-replace name ".stateofus.eth" "") alias)
:identicon identicon
:nickname nickname
:public-key public-key})))
(defn mentionable-contacts [contacts]
(reduce
+2 -9
View File
@@ -4,7 +4,6 @@
[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
@@ -74,8 +73,7 @@
{: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})
preferred-name (get-in db [:multiaccount :preferred-name])]
message (merge pin-message {:pinned-by current-public-key})]
(fx/merge cofx
{:db (cond-> db
pinned
@@ -88,12 +86,7 @@
(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)})
(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}])))))
:pinned (pin-message :pinned)}))))
(fx/defn load-pin-messages
{:events [::load-pin-messages]}
-9
View File
@@ -14,7 +14,6 @@
(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)
@@ -177,7 +176,6 @@
(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)
@@ -185,13 +183,6 @@
(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,7 +31,6 @@
: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})
+2 -3
View File
@@ -328,9 +328,8 @@
;; Information Box
(def closable-information-boxes
"[{:id information box id
:global? true/false (close information box across all profiles)}]"
[])
[{:id :ens-banner
:global? true}]) ;; global? - close information box across all profiles
(defn information-box-id-hash [id public-key global?]
(if global?
+29 -16
View File
@@ -5,12 +5,15 @@
[status-im.utils.fx :as fx]
[status-im.wallet.core :as wallet]
["@react-native-community/netinfo" :default net-info]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[reagent.core :as reagent]))
(fx/defn change-network-status
[{:keys [db] :as cofx} is-connected?]
[{:keys [db] :as cofx} is-connected? reconnected?]
(fx/merge cofx
{:db (assoc db :network-status (if is-connected? :online :offline))}
(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}]))
(when (and is-connected?
(or (not= (count (get-in db [:wallet :accounts]))
(count (get db :multiaccount/accounts)))
@@ -26,29 +29,39 @@
(fx/defn handle-network-info-change
{:events [::network-info-changed]}
[{:keys [db] :as cofx} {:keys [isConnected type details] :as state}]
[{:keys [db] :as cofx} {:keys [isConnected type details isInternetReachable app-went-offline?] :as state}]
(let [old-network-status (:network-status db)
old-network-type (:network/type db)
connectivity-status (if isConnected :online :offline)
status-changed? (= connectivity-status old-network-status)
type-changed? (= type old-network-type)]
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)]
(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-not status-changed?
(change-network-status isConnected))
(when (or status-changed? reconnected?)
(change-network-status isConnected reconnected?))
(when-not type-changed?
(change-network-type old-network-type type (:is-connection-expensive details))))))
(defn add-net-info-listener []
(when net-info
(.addEventListener ^js net-info
#(re-frame/dispatch [::network-info-changed
(js->clj % :keywordize-keys true)]))))
(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?))])))))
(re-frame/reg-fx
::listen-to-network-info
+7 -10
View File
@@ -55,16 +55,13 @@
: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")
: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")})
{: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")})
(defn get-theme-image [k]
(get ui (when (colors/dark?) (keyword (str (name k) "-dark"))) (get ui k)))
+18 -25
View File
@@ -6,36 +6,29 @@
[clojure.string :as string]))
(re-frame/reg-sub
:activity-center/notifications
:<- [:activity-center]
(fn [activity-center]
(:notifications activity-center)))
:activity-center/notifications-read
(fn [db]
(get-in db [:activity-center :notifications-read :data])))
(re-frame/reg-sub
:activity-center/filter-status
:<- [:activity-center]
(fn [activity-center]
(get-in activity-center [:filter :status])))
:activity-center/notifications-unread
(fn [db]
(get-in db [:activity-center :notifications-unread :data])))
(re-frame/reg-sub
:activity-center/filter-type
:<- [:activity-center]
(fn [activity-center]
(get-in activity-center [:filter :type] constants/activity-center-notification-type-no-type)))
:activity-center/current-status-filter
(fn [db]
(get-in db [:activity-center :current-status-filter])))
(re-frame/reg-sub
: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)))
: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)))
(defn- group-notifications-by-date
[notifications]
@@ -71,4 +64,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,7 +228,6 @@
(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,7 +12,6 @@
: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])
@@ -41,8 +40,6 @@
(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,17 +1,15 @@
(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]]))
@@ -30,8 +28,8 @@
[{:keys [type]}]
(case type
constants/activity-center-notification-type-contact-request
:main-icons2/add-user
:main-icons2/placeholder))
:add-user
:placeholder))
(defn activity-context
[{:keys [message last-message type]}]
@@ -79,7 +77,7 @@
nil))
(defn activity-pressable
[notification activity]
[notification & children]
(case (get-in notification [:message :contact-request-state])
constants/contact-request-message-state-accepted
;; NOTE [2022-09-21]: We need to dispatch to
@@ -87,13 +85,14 @@
;; `: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)}])}
activity]
activity))
children]
[:<> children]))
(defn render-notification
[notification index]
[rn/view {:margin-top (if (= 0 index) 0 4)
:padding-horizontal 20}
[rn/view {:flex 1
:flex-direction :column
:margin-top (if (= 0 index) 0 4)}
[activity-pressable notification
[activity-logs/activity-log
(merge {:context (activity-context notification)
@@ -105,108 +104,27 @@
:unread? (not (:read notification))}
(activity-buttons notification))]]])
(defn filter-selector-read-toggle
(defn notifications-list
[]
(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]))
(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}]))
;; 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
[]
(defn activity-center []
(reagent/create-class
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page])
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])
:reagent-render
(fn []
(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]}]))}))
[:<>
[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]])}))
@@ -1,81 +0,0 @@
(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)]]]]))
@@ -1,58 +0,0 @@
(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 false)}
[rn/view {:style (styles/reply-content)}
[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,14 +2,12 @@
(:require [status-im.ui.components.icons.icons :as icons]
[quo.react-native :as rn]
[oops.core :refer [oget]]
[quo.react :as quo.react]
[quo.react :as 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]
@@ -29,7 +27,7 @@
[quo.components.safe-area :as safe-area]))
(defn input-focus [text-input-ref]
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
(some-> ^js (react/current-ref text-input-ref) .focus))
(def panel->icons {:extensions :main-icons/commands
:images :main-icons/photo})
@@ -159,7 +157,7 @@
(quo.react/set-native-props sticker-ref #js {:width nil :right nil})))
(defn reset-input [refs chat-id]
(some-> ^js (quo.react/current-ref (:text-input-ref refs)) .clear)
(some-> ^js (react/current-ref (:text-input-ref refs)) .clear)
(swap! mentions-enabled update :render not)
(swap! input-texts dissoc chat-id))
@@ -206,11 +204,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
@@ -218,23 +216,16 @@
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)
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]
(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]
:db
(-> db
(assoc-in [:chats/cursor chat-id] (:mention-end info))
@@ -339,46 +330,47 @@
[idx item])
@(re-frame/subscribe [:chat/input-with-mentions]))]
^{:key (str idx "_" type "_" text)}
[rn/text (when (= type :mention) {:style {:color quo2.colors/primary-50}}) text])
[rn/text (when (= type :mention) {:style {:color "#0DA4C9"}})
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)]
[rn/touchable-opacity
{:on-press #(re-frame/dispatch [:chat.ui/select-mention text-input-ref user])}
[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]))}
[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))]]))
ens-name?
(assoc :subtitle alias))]))
(def chat-toolbar-height (reagent/atom nil))
(defn autocomplete-mentions-old [text-input-ref bottom]
(defn autocomplete-mentions [text-input-ref bottom]
(let [suggestions @(re-frame/subscribe [:chat/mention-suggestions])]
(when (seq suggestions)
(let [height (+ 16 (* 52 (min 4.5 (count suggestions))))]
@@ -396,22 +388,6 @@
: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)))
@@ -485,12 +461,12 @@
:input-focus #(input-focus text-input-ref)
:set-active set-active-panel}])])]]]))))
(defn calculate-y [context keyboard-shown min-y max-y added-value]
(defn calculate-y [context keyboard-shown min-y max-y]
(if keyboard-shown
(if (= (:state @context) :max)
max-y
(if (< (:y @context) max-y)
(+ (:y @context) added-value)
(:y @context)
(do
(swap! context assoc :state :max)
max-y)))
@@ -498,21 +474,6 @@
(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
@@ -593,30 +554,30 @@
:pdy 0 ;used for gesture
:state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max
:clear 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}]
keyboard-was-shown (atom false)]
(fn []
[:f>
(fn []
(let [reply (<sub [:chats/reply-message])
suggestions (<sub [:chat/mention-suggestions])
(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}
{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 - 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)
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)
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)
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)]
bg-opacity keyboard-shown min-y max-y)]
(quo.react/effect! #(do
(when (and @keyboard-was-shown (not keyboard-shown))
(swap! context assoc :state :min))
@@ -636,29 +597,26 @@
(styles/new-input-bottom-sheet window-height))}
;handle
[rn/view {:style (styles/new-bottom-sheet-handle)}]
[reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply]
[rn/view {:style {:height (- max-y 80 added-value)}}
[rn/view {:style {:height (- max-y 80)}}
[text-input {:chat-id chat-id
:on-content-size-change input-content-change
:sending-image false
:refs refs
:set-active-panel #()}]]]]
;CONTROLS
(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]]])
[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]]]
;black background
[reanimated/view {:style (reanimated/apply-animations-to-style
{:opacity bg-opacity}
(styles/new-bottom-sheet-background window-height))}]
[autocomplete-mentions suggestions]]))])))])
(styles/new-bottom-sheet-background window-height))}]]))])))])
@@ -81,16 +81,15 @@
[icons/icon :main-icons/close-circle {:container-style (styles/close-button)
:color (:icon-02 @quo.colors/theme)}]]]]))
; 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?]
(defn reply-message [{:keys [from identicon content-type contentType parsed-text content]} in-chat-input?]
(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 (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?)}
[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}}
[photos/member-photo from identicon 16]
[quo2.text/text {:weight :semi-bold
:size :paragraph-2
@@ -126,7 +125,7 @@
(defn send-image [images]
[rn/view {:style (styles/reply-container-image)}
[rn/scroll-view {:horizontal true
:style (styles/reply-content false)}
:style (styles/reply-content)}
(for [{:keys [uri]} (vals images)]
^{:key uri}
[rn/image {:source {:uri uri}
@@ -145,8 +144,7 @@
(when-not (= reply @had-reply)
(reset! had-reply reply)
(when reply
;; 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))))
(js/setTimeout #(input-focus text-input-ref) 250))))
(defn reply-message-wrapper-old [reply]
[rn/view {:style {:padding-horizontal 15
@@ -157,6 +155,8 @@
(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,9 +168,10 @@
(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 [_ reply]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply-message-wrapper reply]))))
(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])))))
@@ -108,19 +108,11 @@
:padding-horizontal 10
:flex 1})
(defn reply-content [pin?]
{:padding-horizontal (when-not pin? 10)
(defn reply-content []
{:padding-horizontal 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
@@ -151,8 +143,7 @@
:bottom bottom
:background-color (colors/get-color :ui-background)
:border-top-width 1
:border-top-color (colors/get-color :ui-01)
:z-index 3})
:border-top-color (colors/get-color :ui-01)})
(defn new-input-bottom-sheet [window-height]
(merge {:border-top-left-radius 20
@@ -164,7 +155,7 @@
:height window-height
:flex 1
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)
:z-index 2}
:z-index 1000}
(if platform/ios?
{:shadow-radius 16
:shadow-opacity 1
@@ -185,7 +176,7 @@
{:flex-direction :row
:padding-horizontal 20
:elevation 2
:z-index 2
:z-index 2000
: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
@@ -201,4 +192,4 @@
:bottom 0
:height window-height
:background-color quo2.colors/neutral-95-opa-70
:z-index 1})
:z-index 500})
@@ -4,6 +4,7 @@
[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]
@@ -183,6 +184,15 @@
: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,40 +1,34 @@
(ns status-im.ui.screens.chat.message.message
(: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]
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.fast-image :as fast-image]
[quo.design-system.colors :as colors]
[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.ui.screens.chat.styles.photos :as photos.style]
[status-im.ui.screens.communities.icon :as communities.icon]
[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])
[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.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])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
@@ -95,12 +89,9 @@
timestamp-str]])))
(defview quoted-message
[_ 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))
[_ reply]
[react/view {:style (style/quoted-message-container)}
[components.reply/reply-message reply false]])
(defn render-inline [message-text content-type acc {:keys [type literal destination]}]
(case type
@@ -139,14 +130,13 @@
destination])
"mention"
(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]]])
(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]])
"status-tag"
(conj acc [react/text-class
{:style {:color colors/blue
@@ -229,22 +219,28 @@
(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 10)
(def pin-icon-width 9)
(def pin-icon-height 15)
(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)
(defn pinned-by-indicator [display-photo? pinned-by]
[react/view {:style (style/pin-indicator display-photo?)
:accessibility-label :pinned-by}
[pin-icon]
[quo/text {:size :small
[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
:color :main
:style (style/pin-author-text)}
(pin-author-name pinned-by)]])
@@ -298,8 +294,7 @@
"Author, userpic and delivery wrapper"
[{:keys [last-in-group?
identicon
from in-popover? timestamp-str
deleted-for-me? pinned]
from in-popover? timestamp-str]
:as message} content {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[react/view {:style (style/message-wrapper message)
@@ -310,12 +305,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? pinned)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group?)
[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? pinned)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group?)
[react/view {:style {:flex-direction :row :align-items :center}}
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
@@ -325,19 +320,14 @@
[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
;; 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)
;;MESSAGE 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]]]))
@@ -446,13 +436,6 @@
: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])
@@ -491,34 +474,6 @@
[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])
@@ -626,13 +581,6 @@
: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])
@@ -663,13 +611,6 @@
: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])
@@ -737,7 +678,7 @@
[message-content-wrapper message
[unknown-content-type message]])
(defn chat-message [{:keys [pinned pinned-by mentioned] :as message}]
(defn chat-message [{:keys [display-photo? pinned pinned-by] :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))
@@ -764,12 +705,11 @@
(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,7 +92,6 @@
(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,8 +3,7 @@
[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.typography :as typography]))
[quo2.foundations.colors :as quo2.colors]))
(defn style-message-text
[]
@@ -72,15 +71,30 @@
{:align-self :flex-start
:padding-left 8})
(defn pin-indicator []
(merge {:flex-direction :row}))
(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-container []
{:margin-top 8
:margin-left 68
{:margin-top 2
:justify-content :center
:align-self :flex-start
:align-items :flex-start})
:align-items :flex-start
:padding-left 8})
(defn pinned-by-text-icon-container []
{:flex-direction :row
@@ -94,9 +108,14 @@
:margin-top 1})
(defn pin-author-text []
(merge typography/font-medium
{:color quo2.colors/primary-50
:bottom 2}))
{:margin-left 2
:margin-right 12
:padding-right 0
:left 12
:flex-direction :row
:flex-shrink 1
:align-self :flex-start
:overflow :hidden})
(defn pinned-by-text []
{:margin-left 5})
@@ -108,7 +127,7 @@
(defn message-author-userpic []
(merge
{:width (+ 16 photos/default-size)} ;; 16 is for the padding
{:padding-left 0
{:padding-left 8
:padding-right 8}))
(def delivery-text
@@ -152,17 +171,20 @@
:flex-direction :row-reverse})
(defn message-view
[{:keys [content-type mentioned]}]
[{:keys [content-type mentioned pinned]}]
(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})
:padding-top 6}
:else {:background-color colors/white})
(when (= content-type constants/content-type-emoji)
{:flex-direction :row})))
+35 -71
View File
@@ -419,42 +419,10 @@
: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
@@ -470,43 +438,39 @@
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
: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?])]))
;;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})})]))
(defn back-button []
[quo2.button/button {:type :grey
@@ -602,7 +566,7 @@
[accessory/view {:y position-y
:on-update-inset on-update}
[invitation-bar chat-id]])
[components/autocomplete-mentions-old text-input-ref max-bottom-space]
[components/autocomplete-mentions text-input-ref max-bottom-space]
(when show-input?
;; NOTE: this only accepts two children
[accessory/view {:y position-y
@@ -28,21 +28,14 @@
(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
: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
;; :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)}]}})
(defn plus-button []
(let [logging-in? (<sub [:multiaccounts/login])]
@@ -186,7 +179,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 4 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]]
{:id 3 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]]
[react/scroll-view {:horizontal true
:height 48
:shows-horizontal-scroll-indicator false
@@ -1,140 +1,72 @@
(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 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.markdown.text :as quo2.text]
[quo2.components.buttons.button :as quo2.button]
[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]))
;; 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 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"]])
(defn 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)]])
[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"]])
(defn community-card-page-view [{:keys [name description locked joined
status tokens cover tags] :as community}]
[react/view
{:style
{:flex 1
{:height "100%"
:border-radius 20}}
[react/view (styles/community-cover-container 148)
[react/view (styles/community-cover-container 148)
[react/image
{:source cover
:style {:position :relative
:style {:height 180
:flex 1}}]]
[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)])]
[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)])
[channel-list-component]]])
(defn overview []
(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%"}}
(let [community (<sub [:get-screen-params :community-overview])]
[react/view {:style {}}
[community-card-page-view
(merge community-mock {:joined (:joined community)})]]))
community]]))
@@ -1,17 +1,16 @@
(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 text]
[quo2.components.buttons.button :as button]
[quo2.components.selectors.disclaimer :as disclaimer]
[quo2.components.icon :as icon]
[quo2.components.markdown.text :as quo2.text]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.icon :as quo2.icon]
[status-im.utils.handlers :refer [>evt]]
[quo2.components.tags.context-tags :as context-tags]
;; [quo2.components.tags.context-tags :as quo2.context-tags]
[status-im.communities.core :as communities]
[quo2.foundations.colors :as colors]))
[quo2.foundations.colors :as quo2.colors]))
;; TODO: update with real data
(def community-rules [{:index 1
@@ -47,75 +46,84 @@
:width 18
:margin-left 1
:margin-right 9
:background-color colors/white
:border-color colors/neutral-20
:background-color quo2.colors/white
:border-color quo2.colors/neutral-20
:border-width 1
:border-radius 6}}
[text/text {:style
{:margin-left :auto
:margin-right :auto
:margin-top :auto
:margin-bottom :auto}
:accessibility-label :communities-rule-index
:weight :medium
:size :label}
[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}
(str index)]]
[text/text
[quo2.text/text
{:accessibility-label :communities-rule-title
:weight :semi-bold
:size :paragraph-2}
title]]
[text/text
{:style {:margin-left 28 :margin-top 1}
[quo2.text/text
{:style {:margin-left 28}
: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}}
[text/text {:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
[quo2.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 colors/white
:border-color colors/neutral-20
:background-color quo2.colors/white
:border-color quo2.colors/neutral-20
:border-width 1
:border-radius 8
:border-radius 6
:display :flex
:justify-content :center}}
[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}
[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}
(i18n/label :t/community-rules)]
[community-rules-list community-rules]
[disclaimer/disclaimer
{:container-style {:margin-top 20}
:on-change #(swap! agreed-to-rules? not)}
(i18n/label :t/accept-community-rules)]
[rules-checkbox agreed-to-rules?]
[react/view {:style {:width "100%"
:margin-top 32 :margin-bottom 16
@@ -123,10 +131,8 @@
:flex-direction :row
:align-items :center
:justify-content :space-evenly}}
[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)]]])))
[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)]]])))
+20 -2
View File
@@ -26,10 +26,12 @@
[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.buttons.button :as quo2.button]
[quo2.components.info.information-box :as information-box])
(:require-macros [status-im.utils.views :as views]))
(defn home-tooltip-view []
@@ -187,9 +189,12 @@
[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?]]
hide-home-tooltip? [:hide-home-tooltip?]
information-box-closed? [:information-box-closed? :ens-banner]]
(if (and (empty? items)
(empty? search-filter)
hide-home-tooltip?
@@ -205,6 +210,19 @@
: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,17 +6,29 @@
[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.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]))
(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])
(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))
selected-account (first (filter
#(= account
(:address %))
@@ -25,28 +37,51 @@
[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/view {:style (styles/selected-account-container (:color selected-account))}
[rn/touchable-opacity {:style (styles/selected-account-container (:color selected-account))
:on-press #(swap! show-account-selector? not)}
[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 [legacy-sessions @(re-frame/subscribe [:wallet-connect-legacy/sessions])]
[rn/view {:margin-top 10}
(doall (map print-session-info legacy-sessions))]))
(let [sessions (reagent/atom (<sub [:wallet-connect-legacy/sessions]))
visible-accounts (<sub [:visible-accounts-without-watch-only])]
[list-comp sessions visible-accounts]))
-2
View File
@@ -182,5 +182,3 @@
;; 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
@@ -1,15 +0,0 @@
(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
@@ -0,0 +1,10 @@
(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