Compare commits

..
Author SHA1 Message Date
Omar Basem b00f0971bc velocity gesture test 2023-05-09 14:47:03 +04:00
Omar Basem a6fdc9b118 fix shadow 2023-05-09 13:11:37 +04:00
Omar Basem 4d1ff17ba1 qa review 2023-05-09 12:37:58 +04:00
Omar Basem 8a02803d9f updates 2023-05-09 12:37:58 +04:00
Omar Basem f9ee7e0559 review 2023-05-09 12:37:58 +04:00
Omar Basem 978540d5b7 lint 2023-05-09 12:37:58 +04:00
Omar Basem 3c02518d4a fix 2023-05-09 12:37:57 +04:00
Omar Basem 7bafb3d82b formatting 2023-05-09 12:37:57 +04:00
Omar Basem 59d1383177 lint 2023-05-09 12:37:57 +04:00
Omar Basem 5fb48949ae lint 2023-05-09 12:37:56 +04:00
Omar Basem 78e9102264 review 2023-05-09 12:37:56 +04:00
Omar Basem 2b2170dfff review 2023-05-09 12:37:56 +04:00
Omar Basem ab94f9b017 qa review 2023-05-09 12:37:56 +04:00
Omar Basem 1f5047eea7 review 2023-05-09 12:37:55 +04:00
Omar Basem e6e77e4a58 fix test 2023-05-09 12:37:55 +04:00
Omar Basem 22d9c14ae6 lint 2023-05-09 12:37:55 +04:00
Omar Basem 84bbfe8f9d feat: new composer
review

lint

review

udpates

updates

updates

udpates

updates

updates

lint

updates

composer shell button

updates

updates

updates

updates

updates

updates

updates

updates

updates

updates

updates

updates

updates

lint

review

review

review

review

revert podfile.lock

selectable-input

updates

updates

updates

updates
2023-05-09 12:37:55 +04:00
148 changed files with 2854 additions and 3347 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
agent { label params.AGENT_LABEL }
+2 -2
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
@@ -9,7 +9,7 @@ pipeline {
options {
timestamps()
/* Prevent Jenkins jobs from running forever */
timeout(time: 15, unit: 'MINUTES')
timeout(time: 10, unit: 'MINUTES')
/* Limit builds retained */
buildDiscarder(logRotator(
numToKeepStr: '10',
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.2'
pipeline {
agent {
-1
View File
@@ -18,7 +18,6 @@
[Working on PR together with QA team](pipeline_process.md)
[Debugging](debugging.md)
## Testing
+19
View File
@@ -91,11 +91,30 @@ These guidelines make db.cljs namespaces the place to go when making changes to
- If manual QA is not needed but all tests don't pass, you can ping @churik or @Serhy to confirm that failed E2E tests are not unrelated.
## Enabling debug logs
Calls to `log/debug` will not be printed to the console by default. It can be enabled under "Advanced settings" in the app:
![Enable Debug Logs](images/codebase-structure-and-guidelines/log-settings.png)
## Translations
The app relies on system locale to select a language from the [list of supported languages](https://github.com/status-im/status-mobile/blob/bda73867471cf2bb8a68b1cc27c9f94b92d9a58b/src/status_im/i18n_resources.cljs#L9). It falls back to English in cash the system locale is not supported.
We use Lokalise App to manage [translations](https://translate.status.im/). In case you need to add/remove a key to translations, you only need to change `en.json`. Missing keys fallback to `en.json`. The actual translations will be added by Lokalise.
## re-frisk
re-frisk is a state visualization tool written by our very own Andrey (@flexsurfer). To start re-frisk, execute the following command:
```bash
$ yarn shadow-cljs run re-frisk-remote.core/start
```
or you can also use make:
```bash
$ make run-re-frisk
```
A server will be started at http://localhost:4567. It might show "not connected" at first. Don't worry and just start using the app. The events and state will populate.
## Merging approved PRs
We don't Github's UI to merge. Instead `./scripts/merge-pr.sh` is used to sign and merge PR to `develop`. You first need to enable [GPG signing on you commits](https://github.com/status-im/status-mobile/blob/develop/STARTING_GUIDE.md#configure-gpg-keys-for-signing-commits).
-64
View File
@@ -1,64 +0,0 @@
# Debugging
## Inspecting re-frame with re-frisk
`re-frisk` is a state visualization tool written by our very own Andrey (@flexsurfer). With its help you can inspect the current state of app-db, watch event, etc.
![re-frisk](images/debugging/re-frisk.png)
To start `re-frisk`, execute the following command:
```bash
$ yarn shadow-cljs run re-frisk-remote.core/start
```
or you can also use make:
```bash
$ make run-re-frisk
```
A server will be started at http://localhost:4567. It might show "not connected" at first. Don't worry and just start using the app. The events and state will populate.
More details about re-frisk are on the [project page](https://github.com/flexsurfer/re-frisk).
## Enabling debug logs
Calls to `log/debug` will not be printed to the console by default. It can be enabled under "Advanced settings" in the app:
![Enable Debug Logs](images/debugging/log-settings.png)
## Checking status-go logs
While status mobile works it saves logs from `status-go` to `geth.log` file.
### Checking logs from physical device
To obtain `geth.log` from physical device you need to shake it and in an opened menu select "Share logs".
![Share logs](images/debugging/share-logs.jpeg)
### Checking logs from iOS Simulator
When developing with iOS simulator it is more convenient to see the `geth.log` updates in real-time.
To do this:
- open Activity Monitor
- find the "StatusIm" app and doubleclick it
- in the opened window select "Open files and ports" and find the full path to `geth.log` (note that it won't appear until you login to Status app)
![geth.log path](images/debugging/geth-path.png)
## Tips
### From @ilmotta:
Something I find extremely convenient for Android is to use `adb` to tail logs. I don't use macOS so I don't know if the iOS simulator offers a CLI interface with the same capabilities.
But here's what I use for example:
```
adb shell tail -n 10 -f /storage/emulated/0/Android/data/im.status.ethereum.debug/files/Download/geth.log | grep 'waku.relay'
```
Also to inspect logs in a more flexible manner, instead of the strict output from `make run-metro`, I prefer `adb logcat`. Combined with enabling status-mobile logs in debug by default plus filtering the logs to only what I care during development, I find this helps me inspect the app without running re-frisk because with the debug log level I can already see which events are dispatched (one of the features I like the most from re-frisk).
```
adb logcat | grep 'ReactNativeJS\|StatusModule\|GoLog'
```

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

+2 -2
View File
@@ -234,7 +234,7 @@ PODS:
- React
- react-native-blob-util (0.13.18):
- React-Core
- react-native-blur (4.3.3):
- react-native-blur (4.3.2):
- React-Core
- react-native-camera-kit (8.0.4):
- React
@@ -706,7 +706,7 @@ SPEC CHECKSUMS:
React-logger: 85f4ef06b9723714b2dfa5b0e5502b673b271b58
react-native-background-timer: 1f7d560647b40e6a60b01c452ba29c54bf581fc4
react-native-blob-util: 600972b1782380a5a7d5db61a3817ea32349dae9
react-native-blur: c6d0a1dc2b4b519f7afe3b14d8151998632b6d18
react-native-blur: 4db8c7556566df4e8714c1af5762a4ebc99175d5
react-native-camera-kit: 498a6d111a904834e0824e9073cfadef7303235f
react-native-cameraroll: 88f4e62d9ecd0e1f253abe4f685474f2ea14bfa2
react-native-config: 5330c8258265c1e5fdb8c009d2cabd6badd96727
@@ -1354,12 +1354,11 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
}
@ReactMethod
public void convertToKeycardAccount(final String keyUID, final String accountData, final String options, final String keycardUID, final String password,
final String newPassword, final Callback callback) throws JSONException {
public void convertToKeycardAccount(final String keyUID, final String accountData, final String options, final String password, final String newPassword, final Callback callback) throws JSONException {
final String keyStoreDir = this.getKeyStorePath(keyUID);
executeRunnableStatusGoMethod(() -> {
Statusgo.initKeystore(keyStoreDir);
return Statusgo.convertToKeycardAccount(accountData, options, keycardUID, password, newPassword);
return Statusgo.convertToKeycardAccount(accountData, options, password, newPassword);
}, callback);
}
@@ -692,7 +692,6 @@ RCT_EXPORT_METHOD(reEncryptDbAndKeystore:(NSString *)keyUID
RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID
accountData:(NSString *)accountData
settings:(NSString *)settings
keycardUID:(NSString *)keycardUID
currentPassword:(NSString *)currentPassword
newPassword:(NSString *)newPassword
callback:(RCTResponseSenderBlock)callback) {
@@ -701,7 +700,7 @@ RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID
#endif
NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID];
StatusgoInitKeystore(multiaccountKeystoreDir.path);
NSString *result = StatusgoConvertToKeycardAccount(accountData, settings, keycardUID, currentPassword, newPassword);
NSString *result = StatusgoConvertToKeycardAccount(accountData, settings, currentPassword, newPassword);
callback(@[result]);
}
+1651 -1651
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -367,7 +367,7 @@ com.facebook.soloader:annotation:0.10.1
com.facebook.soloader:nativeloader:0.10.1
com.facebook.soloader:soloader:0.10.1
com.facebook.yoga:proguard-annotations:1.19.0
com.github.Dimezis:BlurView:version-2.0.3
com.github.Dimezis:BlurView:version-2.0.2
com.github.bumptech.glide:annotations:4.12.0
com.github.bumptech.glide:compiler:4.12.0
com.github.bumptech.glide:disklrucache:4.12.0
+3 -3
View File
@@ -310,7 +310,7 @@ https://dl.google.com/dl/android/maven2/com/android/zipflinger/4.1.0/zipflinger-
https://dl.google.com/dl/android/maven2/com/google/android/material/material/1.0.0/material-1.0.0.pom
https://dl.google.com/dl/android/maven2/com/google/android/material/material/1.2.0-alpha03/material-1.2.0-alpha03.pom
https://dl.google.com/dl/android/maven2/com/google/test/platform/core-proto/0.0.2-dev/core-proto-0.0.2-dev.pom
https://jitpack.io/com/github/Dimezis/BlurView/version-2.0.3/BlurView-version-2.0.3.pom
https://jitpack.io/com/github/Dimezis/BlurView/version-2.0.2/BlurView-version-2.0.2.pom
https://jitpack.io/com/github/status-im/function/0.0.1/function-0.0.1.pom
https://jitpack.io/com/github/status-im/status-keycard-java/android/3.1.1/android-3.1.1.pom
https://jitpack.io/com/github/status-im/status-keycard-java/lib/3.1.1/lib-3.1.1.pom
@@ -475,12 +475,12 @@ https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotatio
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.3.1/error_prone_annotations-2.3.1.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.3.2/error_prone_annotations-2.3.2.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.19.0/error_prone_annotations-2.19.0.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.18.0/error_prone_annotations-2.18.0.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_parent/2.0.18/error_prone_parent-2.0.18.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_parent/2.2.0/error_prone_parent-2.2.0.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_parent/2.3.1/error_prone_parent-2.3.1.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_parent/2.3.2/error_prone_parent-2.3.2.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_parent/2.19.0/error_prone_parent-2.19.0.pom
https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_parent/2.18.0/error_prone_parent-2.18.0.pom
https://repo.maven.apache.org/maven2/com/google/flatbuffers/flatbuffers-java/1.12.0/flatbuffers-java-1.12.0.pom
https://repo.maven.apache.org/maven2/com/google/google/1/google-1.pom
https://repo.maven.apache.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.pom
+6 -6
View File
@@ -14,12 +14,12 @@ declare -a REPOS=(
"https://jitpack.io"
)
function nix_prefetch() {
nix store prefetch-file --json "${1}" 2>/dev/null
function nix_fetch() {
nix-prefetch-url --print-path --type sha256 "${1}" 2>/dev/null
}
function get_nix_path() { echo "${1}" | jq -r .storePath; }
function get_nix_sha() { echo "${1}" | jq -r .hash; }
function get_nix_path() { echo "${1}" | tail -n1; }
function get_nix_sha() { echo "${1}" | head -n1; }
function get_sha1() { sha1sum "${1}" | cut -d' ' -f1; }
# Assumes REPOS from repos.sh is available
@@ -44,7 +44,7 @@ function fetch_and_template_file() {
local OBJ_URL OBJ_NIX_FETCH_OUT OBJ_NAME OBJ_PATH
OBJ_URL="${REPO_URL}/${PKG_PATH}/${FILENAME}"
if ! OBJ_NIX_FETCH_OUT=$(nix_prefetch "${OBJ_URL}"); then
if ! OBJ_NIX_FETCH_OUT=$(nix_fetch "${OBJ_URL}"); then
echo " ! Failed to fetch: ${OBJ_URL}" >&2
return 1
fi
@@ -85,7 +85,7 @@ PKG_PATH="${PKG_URL_NO_EXT#"${REPO_URL}/"}"
PKG_PATH="$(dirname "${PKG_PATH}")"
# Both JARs and AARs have a POM
POM_NIX_FETCH_OUT=$(nix_prefetch "${PKG_URL_NO_EXT}.pom")
POM_NIX_FETCH_OUT=$(nix_fetch "${PKG_URL_NO_EXT}.pom")
POM_PATH=$(get_nix_path "${POM_NIX_FETCH_OUT}")
POM_NAME=$(basename "${PKG_URL_NO_EXT}.pom")
if [[ -z "${POM_PATH}" ]]; then
+1 -1
View File
@@ -16,7 +16,7 @@
"@babel/preset-typescript": "^7.17.12",
"@react-native-async-storage/async-storage": "^1.17.9",
"@react-native-community/audio-toolkit": "git+https://github.com/tbenr/react-native-audio-toolkit.git#refs/tags/v2.0.3-status-v6",
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.3-status",
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.2-status",
"@react-native-community/cameraroll": "git+https://github.com/status-im/react-native-cameraroll.git#refs/tags/v4.0.4-status.0",
"@react-native-community/clipboard": "^1.2.2",
"@react-native-community/hooks": "^2.5.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 587 KiB

+2 -15
View File
@@ -39,22 +39,9 @@ chmod 644 ${SECRETS_FILE_PATH}
# If secrets file was created we want to remove it.
trap "rm -vf ${SECRETS_FILE_PATH}" EXIT ERR INT QUIT
# Names of variables containing secrets to save in env file.
SECRETS_ENV_VARS=(
'ALCHEMY_ARBITRUM_GOERLI_TOKEN'
'ALCHEMY_ARBITRUM_MAINNET_TOKEN'
'ALCHEMY_OPTIMISM_GOERLI_TOKEN'
'ALCHEMY_OPTIMISM_MAINNET_TOKEN'
'INFURA_TOKEN'
'INFURA_TOKEN_SECRET'
'OPENSEA_API_KEY'
'POKT_TOKEN'
)
# Secrets like this can't be passed via args or they end up in derivation.
for SECRET_VAR_NAME in "${SECRETS_ENV_VARS[@]}"; do
[[ -n "${!SECRET_VAR_NAME}" ]] && append_env_export "${SECRET_VAR_NAME}"
done
if [[ -n "${POKT_TOKEN}" ]]; then append_env_export 'POKT_TOKEN'; fi
if [[ -n "${OPENSEA_API_KEY}" ]]; then append_env_export 'OPENSEA_API_KEY'; fi
# If no secrets were passed there's no need to pass the 'secretsFile'.
if [[ -s "${SECRETS_FILE_PATH}" ]]; then
-1
View File
@@ -513,7 +513,6 @@
key-uid
(types/clj->json multiaccount-data)
(types/clj->json settings)
""
current-password#
new-password
callback))
+10
View File
@@ -0,0 +1,10 @@
(ns native-module.core-test
(:require [cljs.test :refer [deftest is testing]]
[native-module.core :as native-module]))
(deftest identicon-test
(testing "check if identicon test works"
(is
(=
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAjklEQVR4nOzXsQmAMBQGYRV3cUAdQwd0Gm2sJIWSBI6f+0oR8XjwSKYhhCE0htAYQmMITUzI/PXF49yv0vN12cYWP1L7/ZiJGEJjCE31xvm7bXptv5iJGEJjCE31WasVz1oPQ2gMoWlyuyvpfaN8i5mIITSG0BhCYwiNIeokZiKG0BhCYwiNITR3AAAA//+A3RtWaKqXgQAAAABJRU5ErkJggg=="
(native-module/identicon "a")))))
+1 -1
View File
@@ -11,7 +11,7 @@
(defn neutral-border-color
[in-blur-view? override-theme]
(if in-blur-view?
(colors/theme-colors colors/neutral-80-opa-5 colors/white-opa-10 override-theme)
(colors/theme-colors colors/white-opa-10 colors/neutral-80-opa-5 override-theme)
(colors/theme-colors colors/neutral-20 colors/neutral-80 override-theme)))
(def active-background-color (colors/custom-color :blue 50 10))
@@ -1,59 +1,8 @@
(ns quo2.components.inputs.recovery-phrase.component-spec
(:require [clojure.string :as string]
[oops.core :as oops]
[quo2.components.inputs.recovery-phrase.view :as recovery-phrase]
(:require [quo2.components.inputs.recovery-phrase.view :as recovery-phrase]
[test-helpers.component :as h]))
(h/describe "Recovery phrase input"
(h/test "Default render"
(h/render [recovery-phrase/recovery-phrase-input {}])
(h/is-truthy (h/get-by-label-text :recovery-phrase-input)))
(h/test "Renders specified text"
(let [text-expected "My custom text"]
(h/render [recovery-phrase/recovery-phrase-input {} text-expected])
(h/is-equal (oops/oget (h/get-by-label-text :recovery-phrase-input) "props" "children")
text-expected)))
(h/test "Text changes and dispatches on-change-text"
(let [new-text "New text"
on-change-mock (h/mock-fn)
get-new-text #(-> % (oops/oget "props" "onChangeText" "mock" "calls") (aget 0 0))]
(h/render [recovery-phrase/recovery-phrase-input {:on-change-text on-change-mock}
"Old text"])
(h/fire-event :change-text (h/get-by-label-text :recovery-phrase-input) new-text)
(h/is-equal (get-new-text (h/get-by-label-text :recovery-phrase-input)) new-text)
(h/was-called on-change-mock)))
(h/describe "Error text"
(h/test "Marked when words doesn't satisfy a predicate"
(h/render [recovery-phrase/recovery-phrase-input
{:mark-errors? true
:error-pred #(>= (count %) 5)}
"Text with some error words that don't satisfy the predicate"])
(let [children-text-nodes (-> (h/get-by-label-text :recovery-phrase-input)
(oops/oget "props" "children" "props" "children")
(js->clj :keywordize-keys true))
{:keys [ok-words error-words]} (group-by #(if (string? %) :ok-words :error-words)
children-text-nodes)]
(h/is-equal (apply str ok-words) "Text with some that the ")
(h/is-truthy (= (map #(-> % :props :argv second) error-words)
["error" "words" "don't" "satisfy" "predicate"]))))
(h/test "Marked when words exceed the limit given"
(h/render [recovery-phrase/recovery-phrase-input
{:mark-errors? true
:word-limit 4}
"these are ok words, these words exceed the limit"])
(let [children-text-nodes (-> (h/get-by-label-text :recovery-phrase-input)
(oops/oget "props" "children" "props" "children")
(js->clj :keywordize-keys true))
{:keys [ok-words error-words]} (group-by #(if (string? %) :ok-words :error-words)
children-text-nodes)]
(h/is-equal (string/trim (apply str ok-words))
"these are ok words,")
(h/is-equal (->> error-words
(map #(-> % :props :argv second))
(interpose " ")
(apply str))
"these words exceed the limit")))))
(h/is-truthy (h/get-by-label-text :recovery-phrase-input))))
@@ -31,8 +31,7 @@
set-default #(reset! state :default)]
(fn [{:keys [customization-color override-theme blur? on-focus on-blur mark-errors?
error-pred word-limit]
:or {customization-color :blue
error-pred (constantly false)}
:or {customization-color :blue}
:as props}
text]
(let [extra-props (apply dissoc props custom-props)]
@@ -173,7 +173,6 @@
reached-max-duration? (atom false)
touch-timestamp (atom nil)
disabled? (atom false)
app-state-listener (atom nil)
rec-options
(merge
audio/default-recorder-options
@@ -513,13 +512,7 @@
(on-init reset-recorder))
(when audio-file
(let [filename (last (string/split audio-file "/"))]
(reload-player filename)))
(reset! app-state-listener
(.addEventListener rn/app-state
"change"
#(when (= % "background")
(reset! playing-audio? false))))
#(.remove @app-state-listener)))
(reload-player filename)))))
[rn/view
{:style style/bar-container
:pointer-events :box-none}
@@ -12,6 +12,12 @@
(colors/theme-colors colors/neutral-80-opa-5 colors/white-opa-5 override-theme)
(colors/theme-colors colors/neutral-10 colors/neutral-90 override-theme))})
(def context-tag-image
{:width 20
:border-radius 10
:background-color :white
:height 20})
(defn context-tag-icon-color
[blur?]
(if blur?
@@ -1,6 +1,5 @@
(ns quo2.components.tags.context-tag.view
(:require [quo2.components.avatars.user-avatar.view :as user-avatar]
[quo2.components.avatars.group-avatar :as group-avatar]
(:require [quo2.components.avatars.group-avatar :as group-avatar]
[quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[quo2.components.tags.context-tag.style :as style]
@@ -44,11 +43,9 @@
:size :paragraph-2
:style (assoc text-style :justify-content :center)}]
[base-tag (assoc-in params [:style :padding-left] 3)
[user-avatar/user-avatar
{:full-name name
:profile-picture photo
:size :xxs
:status-indicator? false}]
[rn/image
{:style style/context-tag-image
:source photo}]
[rn/view {:style style/context-tag-text-container}
[text/text text-params (str " " name)]
(when channel-name
@@ -61,7 +58,7 @@
(defn user-avatar-tag
[params username photo]
[context-tag params photo username])
[context-tag params {:uri photo} username])
(defn audio-tag
[duration params]
-5
View File
@@ -130,11 +130,6 @@
(when (and player (.-canPlay ^js player))
(.-currentTime ^js player)))
(defn set-player-wake-lock
[player wake-lock?]
(when player
(set! (.-wakeLock player) wake-lock?)))
(defn toggle-playpause-player
[player on-play on-pause on-error]
(when (and player (.-canPlay ^js player))
+10 -9
View File
@@ -70,12 +70,13 @@
[{:keys [db] :as cofx} text-input-ref {:keys [primary-name searched-text match public-key] :as user}]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
method "wakuext_chatMentionSelectMention"
params [chat-id text primary-name public-key]]
method "wakuext_chatMentionNewInputTextWithMention"
params [chat-id text primary-name]]
{:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-select-mention-success %
primary-name match searched-text public-key])
:on-success #(rf/dispatch [:mention/on-new-input-text-with-mentions-success %
primary-name text-input-ref match searched-text
public-key])
:on-error #(rf/dispatch [:mention/on-error
{:method method
:params params} %])}]}))
@@ -249,21 +250,21 @@
[{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [{:keys [input-text metadata]} (get-in db [:chat/inputs current-chat-id])
editing-message (:editing-message metadata)
method "wakuext_chatMentionReplaceWithPublicKey"
method "wakuext_chatMentionCheckMentions"
params [current-chat-id input-text]]
{:json-rpc/call [{:method method
:params params
:on-error #(rf/dispatch [:mention/on-error {:method method :params params} %])
:on-success #(rf/dispatch [:mention/on-replace-with-public-key-success
:on-success #(rf/dispatch [:mention/on-check-mentions-success
current-chat-id
editing-message
input-text
%])}]}))
(rf/defn on-replace-with-public-key-success
{:events [:mention/on-replace-with-public-key-success]}
(rf/defn on-check-mentions-success
{:events [:mention/on-check-mentions-success]}
[{:keys [db] :as cofx} current-chat-id editing-message input-text new-text]
(log/debug "[mentions] on-replace-with-public-key-success"
(log/debug "[mentions] on-check-mentions-success"
{:chat-id current-chat-id
:editing-message editing-message
:input-text input-text
+76 -11
View File
@@ -1,7 +1,11 @@
(ns status-im.chat.models.mentions
(:require [clojure.set :as set]
[quo.react :as react]
[quo.react-native :as rn]
[re-frame.core :as re-frame]
[utils.re-frame :as rf]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[native-module.core :as native-module]))
(defn- transfer-input-segments
[segments]
@@ -122,21 +126,47 @@
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(rf/defn on-select-mention-success
{:events [:mention/on-select-mention-success]}
[{:keys [db] :as cofx} result primary-name match searched-text public-key]
(log/debug "[mentions] on-select-mention-success"
(rf/defn recheck-at-idxs
[{:keys [db]} public-key]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
params [chat-id text public-key]
method "wakuext_chatMentionRecheckAtIdxs"]
{:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-recheck-at-idxs-success %])
:on-error #(rf/dispatch [:mention/on-error
{:method method
:params params} %])}]}))
(rf/defn on-recheck-at-idxs-success
{:events [:mention/on-recheck-at-idxs-success]}
[{:keys [db]} result]
(log/debug "[mentions] on-recheck-at-idxs-success" {:result result})
(let [{:keys [input-segments state chat-id]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(rf/defn reset-text-input-cursor
[_ ref cursor]
{::reset-text-input-cursor [ref cursor]})
(rf/defn on-new-input-text-with-mentions-success
{:events [:mention/on-new-input-text-with-mentions-success]}
[{:keys [db] :as cofx} result primary-name text-input-ref match searched-text public-key]
(log/debug "[mentions] on-new-input-text-with-mentions-success"
{:result result
:primary-name primary-name
:match match
:searched-text searched-text
:public-key public-key})
(let [{:keys [new-text chat-id state input-segments]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments)
(assoc-in [:chats/mention-suggestions chat-id] nil))
:dispatch [:chat.ui/set-chat-input-text new-text chat-id]}))
(let [{:keys [new-text chat-id]} (transfer-mention-result result)]
(rf/merge
cofx
{:db (assoc-in db [:chats/mention-suggestions chat-id] nil)
:dispatch [:chat.ui/set-chat-input-text new-text chat-id]}
(recheck-at-idxs public-key))))
(rf/defn clear-suggestions
[{:keys [db]}]
@@ -159,3 +189,38 @@
:on-error #(log/error "Error while calling wakuext_chatMentionClearMentions"
{:error %})}]}
(clear-suggestions))))
(rf/defn check-selection
{:events [:mention/on-selection-change]}
[{:keys [db]}
{:keys [start end]}]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
params [chat-id text start end]
method "wakuext_chatMentionHandleSelectionChange"]
(when text
(log/debug "[mentions] check-selection" {:params params})
{:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-handle-selection-change-success %])
:on-error #(rf/dispatch [:mention/on-error
{:method method
:params params} %])}]})))
(rf/defn on-check-selection-success
{:events [:mention/on-handle-selection-change-success]}
[{:keys [db]} result]
(log/debug "[mentions] on-check-selection-success" {:result result})
(let [{:keys [state chat-id mentionable-users input-segments]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mention-suggestions chat-id] mentionable-users)
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(re-frame/reg-fx
::reset-text-input-cursor
(fn [[ref cursor]]
(when ref
(native-module/reset-keyboard-input
(rn/find-node-handle (react/current-ref ref))
cursor))))
@@ -20,6 +20,7 @@
:clock clock-value
:alias "alias"
:name "name"
:identicon "identicon"
:from "from"}]
;; <- cursor
;; <- message
@@ -106,6 +107,7 @@
:clock (- clock-value 2)
:alias "alias"
:name "name"
:identicon "identicon"
:from "from"}
result (dissoc (message/receive-many
cofx
+4 -2
View File
@@ -3,7 +3,8 @@
[clojure.string :as string]
[status-im2.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.utils.gfycat.core :as gfycat]))
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon]))
(defn public-key->new-contact
[public-key]
@@ -11,6 +12,7 @@
{:alias alias
:name alias
:primary-name alias
:identicon (identicon/identicon public-key)
:public-key public-key}))
(defn public-key-and-ens-name->new-contact
@@ -57,7 +59,7 @@
[members admins contacts {:keys [public-key preferred-name name] :as current-account}]
(let [current-contact (some->
current-account
(select-keys [:name :preferred-name :public-key :images])
(select-keys [:name :preferred-name :public-key :identicon :images])
(set/rename-keys {:name :alias :preferred-name :name})
(assoc :primary-name (or preferred-name name)))
all-contacts (cond-> contacts
+9 -2
View File
@@ -1,11 +1,13 @@
(ns status-im.contact.db-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.contact.db :as contact.db]
[status-im.utils.gfycat.core :as gfycat]))
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon]))
(deftest contacts-subs
(testing "get-all-contacts-in-group-chat"
(with-redefs [gfycat/generate-gfy (constantly "generated")]
(with-redefs [gfycat/generate-gfy (constantly "generated")
identicon/identicon (constantly "generated")]
(let
[chat-contact-ids
#{"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"
@@ -19,6 +21,7 @@
{:last-updated 0
:name "User B"
:primary-name "User B"
:identicon "photo1"
:last-online 0
:public-key
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"}}
@@ -28,6 +31,7 @@
:sharing-usage-data? false
:primary-name "User A"
:name "User A"
:identicon "photo2"
:public-key
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}]
(is
@@ -38,17 +42,20 @@
current-multiaccount)
[{:name "generated"
:primary-name "generated"
:identicon "generated"
:alias "generated"
:admin? true
:public-key
"0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917"}
{:alias "User A"
:primary-name "User A"
:identicon "photo2"
:public-key
"0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"}
{:last-updated 0
:name "User B"
:primary-name "User B"
:identicon "photo1"
:last-online 0
:public-key
"0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f"}]))))))
+1
View File
@@ -79,6 +79,7 @@
:emoji (.-emoji chat)
:timestamp (.-timestamp chat)
:alias (.-alias chat)
:identicon (.-identicon chat)
:muted (.-muted chat)
:joined (.-joined chat)
:muted-till (.-mutetill chat)
+6 -2
View File
@@ -62,8 +62,7 @@
[status-im2.common.theme.core :as theme]
[react-native.core :as rn]
[react-native.platform :as platform]
status-im2.contexts.chat.home.events
status-im2.contexts.communities.home.events))
status-im2.contexts.chat.home.events))
(re-frame/reg-fx
:dismiss-keyboard
@@ -109,6 +108,11 @@
[_]
{:dismiss-keyboard nil})
(rf/defn identicon-generated
{:events [:identicon-generated]}
[{:keys [db]} path identicon]
{:db (assoc-in db path identicon)})
(rf/defn gfycat-generated
{:events [:gfycat-generated]}
[{:keys [db]} path gfycat]
+5 -3
View File
@@ -339,10 +339,11 @@
(let [{:keys [key-uid encryption-public-key whisper-private-key]
:as account-data}
(js->clj data :keywordize-keys true)
{:keys [name]} (get-in db [:multiaccounts/multiaccounts key-uid])
{:keys [identicon name]} (get-in db [:multiaccounts/multiaccounts key-uid])
key-uid (get-in db [:keycard :application-info :key-uid])
multiaccount-data (types/clj->json {:name name
:key-uid key-uid})
multiaccount-data (types/clj->json {:name name
:key-uid key-uid
:identicon identicon})
save-keys? (get-in db [:multiaccounts/login :save-password?])]
(rf/merge cofx
{:db
@@ -359,6 +360,7 @@
(update :multiaccounts/login assoc
:password encryption-public-key
:key-uid key-uid
:identicon identicon
:name name))
:keycard/login-with-keycard {:multiaccount-data multiaccount-data
+1 -1
View File
@@ -200,7 +200,7 @@
(.remove listener)))
(re-frame/reg-fx
:keycard/generate-name
:keycard/generate-name-and-photo
(fn [{:keys [public-key on-success]}]
(native-module/gfycat-identicon-async
public-key
+8 -6
View File
@@ -151,12 +151,13 @@
:multiaccounts-stack
[:multiaccounts
:keycard-login-pin])
(let [{:keys [name]} (get-in db [:multiaccounts/multiaccounts key-uid])
multiaccount-data (types/clj->json {:name name
:key-uid key-uid})
account-data {:key-uid key-uid
:encryption-public-key encryption-public-key
:whisper-private-key whisper-private-key}]
(let [{:keys [identicon name]} (get-in db [:multiaccounts/multiaccounts key-uid])
multiaccount-data (types/clj->json {:name name
:key-uid key-uid
:identicon identicon})
account-data {:key-uid key-uid
:encryption-public-key encryption-public-key
:whisper-private-key whisper-private-key}]
{:db
(-> db
(assoc-in [:keycard :pin :status] nil)
@@ -167,6 +168,7 @@
(update :multiaccounts/login assoc
:password encryption-public-key
:key-uid key-uid
:identicon identicon
:name name
:save-password? true))
:keycard/login-with-keycard
+13 -9
View File
@@ -160,6 +160,7 @@
(let [{{:keys [multiaccount secrets flow]} :keycard} db
{:keys [address
name
identicon
public-key
whisper-public-key
wallet-public-key
@@ -174,17 +175,18 @@
recovered]}
multiaccount
{:keys [pairing paired-on]} secrets
{:keys [name]}
{:keys [name identicon]}
(if (nil? name)
;; name might have been generated during recovery via passphrase
(get-in db [:intro-wizard :derived constants/path-whisper-keyword])
{:name name})]
{:name name
:identicon identicon})]
;; if a name is still `nil` we have to generate it before multiaccount's
;; creation otherwise spec validation will fail
(if (nil? name)
{:keycard/generate-name
{:keycard/generate-name-and-photo
{:public-key whisper-public-key
:on-success ::on-name-generated}}
:on-success ::on-name-and-photo-generated}}
(rf/merge cofx
{:db (-> db
(assoc-in [:keycard :setup-step] nil)
@@ -197,7 +199,8 @@
constants/path-whisper-keyword
{:public-key whisper-public-key
:address (eip55/address->checksum whisper-address)
:name name}
:name name
:identicon identicon}
constants/path-default-wallet-keyword
{:public-key wallet-public-key
:address (eip55/address->checksum wallet-address)}}
@@ -379,11 +382,11 @@
{:on-card-connected :keycard/load-recovering-key-screen
:handler (common/dispatch-event :keycard/import-multiaccount)}))
(rf/defn on-name-generated
{:events [::on-name-generated]
(rf/defn on-name-and-photo-generated
{:events [::on-name-and-photo-generated]
:interceptors [(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]}
[{:keys [db] :as cofx} whisper-name]
[{:keys [db] :as cofx} whisper-name identicon]
(rf/merge
cofx
{:db (update-in db
@@ -391,5 +394,6 @@
(fn [multiacc]
(assoc multiacc
:recovered (get db :recovered-account?)
:name whisper-name)))}
:name whisper-name
:identicon identicon)))}
(create-keycard-multiaccount)))
+5 -2
View File
@@ -268,9 +268,12 @@
public-key (get-in derived-data [constants/path-whisper-keyword :public-key])]
(native-module/gfycat-identicon-async
public-key
(fn [name _]
(fn [name photo-path]
(let [derived-data-extended
(update derived-data constants/path-whisper-keyword assoc :name name)]
(update derived-data
constants/path-whisper-keyword
merge
{:name name :identicon photo-path})]
(reset! derived-acc
{:root-key root-data
:derived derived-data-extended})))))))))))
+17 -4
View File
@@ -9,6 +9,7 @@
[quo2.foundations.colors :as colors]
[status-im2.constants :as constants]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon]
[status-im2.setup.hot-reload :as hot-reload]
[status-im2.common.theme.core :as theme]
[taoensso.timbre :as log]
@@ -47,11 +48,23 @@
(gfycat/generate-gfy identity))]
[(:primary-name contact) (:secondary-name contact)])))
(def photo-quality-thumbnail :thumbnail)
(def photo-quality-large :large)
(defn displayed-photo
[{:keys [images]}]
(or (:thumbnail images)
(:large images)
(first images)))
"If a photo, a image or an images array is set use it, otherwise fallback on identicon or generate"
[{:keys [images identicon public-key]}]
(cond
(pos? (count images))
(:uri (or (photo-quality-thumbnail images)
(photo-quality-large images)
(first images)))
(not (string/blank? identicon))
identicon
:else
(identicon/identicon public-key)))
(re-frame/reg-fx
::webview-debug-changed
+8 -3
View File
@@ -71,11 +71,11 @@
public-key (get-in derived-data [constants/path-whisper-keyword :public-key])]
(native-module/gfycat-identicon-async
public-key
(fn [name _]
(fn [name identicon]
(let [derived-whisper (derived-data constants/path-whisper-keyword)
derived-data-extended (assoc-in derived-data
[constants/path-whisper-keyword]
(assoc derived-whisper :name name))]
(merge derived-whisper {:name name :identicon identicon}))]
(re-frame/dispatch [::store-multiaccount-success key-code derived-data-extended]))))))]}))
(re-frame/reg-fx
@@ -154,12 +154,13 @@
:wallet true
:path constants/path-default-wallet
:name (i18n/label :t/main-account)})
(let [{:keys [compressed-key public-key address name]}
(let [{:keys [compressed-key public-key address name identicon]}
(get-in multiaccount [:derived constants/path-whisper-keyword])]
{:public-key public-key
:compressed-key compressed-key
:address (eip55/address->checksum address)
:name name
:identicon identicon
:path constants/path-whisper
:chat true})])
@@ -173,11 +174,13 @@
(let [[wallet-account
{:keys [public-key
compressed-key
identicon
name]} :as accounts-data]
(prepare-accounts-data
multiaccount)
multiaccount-data {:name name
:address address
:identicon identicon
:key-uid key-uid
:keycard-pairing keycard-pairing}
keycard-multiaccount? (boolean keycard-pairing)
@@ -199,6 +202,7 @@
constants/path-wallet-root-keyword
:address])
:name name
:identicon identicon
;; public key of the chat account
:public-key public-key
;; compressed key of the chat account
@@ -227,6 +231,7 @@
db (assoc db
:multiaccounts/login {:key-uid key-uid
:name name
:identicon identicon
:password password
:creating? true
:processing true}
+13 -11
View File
@@ -168,23 +168,25 @@
(rf/defn login
{:events [:multiaccounts.login.ui/password-input-submitted]}
[{:keys [db]}]
(let [{:keys [key-uid password name]} (:multiaccounts/login db)]
(let [{:keys [key-uid password name identicon]} (:multiaccounts/login db)]
{:db (-> db
(assoc-in [:multiaccounts/login :processing] true)
(dissoc :intro-wizard :recovered-account?)
(update :keycard dissoc :flow))
::login [key-uid
(types/clj->json {:name name
:key-uid key-uid})
(types/clj->json {:name name
:key-uid key-uid
:identicon identicon})
(ethereum/sha3 (security/safe-unmask-data password))]}))
(rf/defn export-db-submitted
{:events [:multiaccounts.login.ui/export-db-submitted]}
[{:keys [db]}]
(let [{:keys [key-uid password name]} (:multiaccounts/login db)]
(let [{:keys [key-uid password name identicon]} (:multiaccounts/login db)]
{::export-db [key-uid
(types/clj->json {:name name
:key-uid key-uid})
(types/clj->json {:name name
:key-uid key-uid
:identicon identicon})
(ethereum/sha3 (security/safe-unmask-data password))
(fn [path]
(when platform/ios?
@@ -196,10 +198,11 @@
(rf/defn import-db-submitted
{:events [:multiaccounts.login.ui/import-db-submitted]}
[{:keys [db]}]
(let [{:keys [key-uid password name]} (:multiaccounts/login db)]
(let [{:keys [key-uid password name identicon]} (:multiaccounts/login db)]
{::import-db [key-uid
(types/clj->json {:name name
:key-uid key-uid})
(types/clj->json {:name name
:key-uid key-uid
:identicon identicon})
(ethereum/sha3 (security/safe-unmask-data password))]}))
(rf/defn finish-keycard-setup
@@ -739,8 +742,7 @@
(merge
{:db (update db :keycard dissoc :application-info)}
(when keycard-multiaccount? {:navigate-to :keycard-login-pin}))
(open-login (select-keys multiaccount
[:key-uid :name :public-key :images :customization-color])))))
(open-login (select-keys multiaccount [:key-uid :name :public-key :identicon :images])))))
(rf/defn hide-keycard-banner
{:events [:hide-keycard-banner]}
@@ -5,6 +5,8 @@
:tags #{}
:address "2f88d65f3cb52605a54a833ae118fb1363acccd2"
:name "Darkviolet Lightgreen Halcyon"
:identicon
"data:image/png;base64iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAADAFBMVEX///+M2KwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADdPOdBAAABAHRSTlP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKmfXxgAABnNJREFUeNoBaAaX+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5UBARL8TK8AAAAASUVORK5CYII="
:added? true
:last-online 0
:public-key
@@ -12,6 +14,8 @@
{:last-updated 1547271764000
:address "b267ff8336ac10b3a1986c04a70ff91fb03d0b78"
:name "rv"
:identicon
"data:image/png;base64iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAADAFBMVEX////VjNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwYzy6AAABAHRSTlP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKmfXxgAABnNJREFUeNoBaAaX+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAAAAAAEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEAAAAAAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQAAAAABAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAAAAAAEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6IYA4bRtf+EAAAAASUVORK5CYII="
:added? true
:last-online 0
:public-key
@@ -97,6 +101,8 @@
:xdai #{}}
:preview-privacy? true
:fleet :eth.prod
:identicon
"data:image/png;base64iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAADAFBMVEX////YsYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPGFwxAAABAHRSTlP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKmfXxgAABnNJREFUeNoBaAaX+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAQEBAQAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAABAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAQEBAQAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAABAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAQEBAQAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAABAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAQEBAQAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAABAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAAAAAAEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEAAAAAAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQAAAAABAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAAAAAAEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAloYA4a9rBHIAAAAASUVORK5CYII="
:wallet-set-up-passed? false
:public-key
"0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c"
@@ -10,13 +10,14 @@
(deftest on-password-input-submitted
(testing
"handling :multiaccounts.login.ui/password-input-submitted event"
(let [cofx {:db {:multiaccounts/login {:key-uid "key-uid"
:password "password"
:name "user"}}}
(let [cofx {:db {:multiaccounts/login {:key-uid "key-uid"
:password "password"
:name "user"
:identicon "photo"}}}
efx (login.core/login cofx)]
(testing "Change multiaccount."
(is (= (::login.core/login efx)
["key-uid" "{\"name\":\"user\",\"key-uid\":\"key-uid\"}"
["key-uid" "{\"name\":\"user\",\"key-uid\":\"key-uid\",\"identicon\":\"photo\"}"
(ethereum/sha3 "password")])))
(testing "start activity indicator"
(is (= (get-in efx [:db :multiaccounts/login :processing]) true))))))
@@ -108,9 +108,12 @@
public-key (get-in derived-data [constants/path-whisper-keyword :public-key])]
(native-module/gfycat-identicon-async
public-key
(fn [name _]
(fn [name identicon]
(let [derived-data-extended
(update derived-data constants/path-whisper-keyword assoc :name name)]
(update derived-data
constants/path-whisper-keyword
merge
{:name name :identicon identicon})]
(re-frame/dispatch [success-event root-data derived-data-extended]))))))))))))
(rf/defn show-existing-multiaccount-alert
+3 -3
View File
@@ -195,10 +195,10 @@
(rf/defn send-installation-messages
{:events [:pairing.ui/synchronize-installation-pressed]}
[{:keys [db]}]
(let [multiaccount (:multiaccount db)
{:keys [name preferred-name]} multiaccount]
(let [multiaccount (:multiaccount db)
{:keys [name preferred-name identicon]} multiaccount]
{:json-rpc/call [{:method "wakuext_syncDevices"
:params [(or preferred-name name)]
:params [(or preferred-name name) identicon]
:on-success #(log/debug "successfully synced devices")}]}))
(defn installation<-rpc
+1 -1
View File
@@ -33,7 +33,7 @@
;; in case of an unknown error we show the
;; error
(if (= error "file is not a database")
(i18n/label :t/oops-wrong-password)
(i18n/label :t/wrong-password)
error)))}
(= (:view-id db) :progress)
(assoc :dispatch [:navigate-to :login]))
@@ -2,7 +2,6 @@
(:require [clojure.string :as string]
[quo.design-system.colors :as colors]
[quo.react-native :as rn]
[quo2.core :as quo]
[re-frame.core :as re-frame.core]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.styles :as styles]
@@ -48,13 +47,19 @@
(defn profile-photo-plus-dot-view
[{:keys [public-key photo-container photo-path community?]}]
(let [photo-container (if (nil? photo-container)
(let [photo-path (if (nil? photo-path)
@(re-frame.core/subscribe [:chats/photo-path public-key])
photo-path)
photo-container (if (nil? photo-container)
styles/container-chat-list
photo-container)
size (:width photo-container)
identicon? (and photo-path
(string/starts-with? photo-path "data:image/png;base64,"))
dot-styles (visibility-status-utils/icon-visibility-status-dot
public-key
size)
size
identicon?)
dot-accessibility-label (:accessibility-label dot-styles)]
[rn/view
{:style photo-container
@@ -142,13 +147,9 @@
[photos/photo (multiaccounts/displayed-photo contact) styles]])
(defn contact-icon-contacts-tab
[{:keys [primary-name] :as contact}]
[photo-path]
[rn/view styles/container-chat-list
[quo/user-avatar
{:full-name primary-name
:profile-picture (multiaccounts/displayed-photo contact)
:size :small
:status-indicator? false}]])
[photos/photo photo-path {:size 40}]])
(defn dapp-icon-permission
[contact size]
@@ -194,10 +195,9 @@
:public-key public-key
:photo-container (:container styles)
:community? community?}]
[rn/view {:accessibility-label :chat-icon}
(if (string/blank? emoji)
[default-chat-icon name styles]
[emoji-chat-icon emoji styles])])
(if (string/blank? emoji)
[default-chat-icon name styles]
[emoji-chat-icon emoji styles]))
(when edit?
[rn/view {:style (styles/chat-icon-profile-edit)}
[icons/tiny-icon :tiny-icons/tiny-edit {:color colors/white-persist}]])]))
+23 -3
View File
@@ -1,5 +1,7 @@
(ns status-im.ui.screens.chat.photos
(:require [quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.photos :as style]))
@@ -9,14 +11,32 @@
(fn [photo-path size accessibility-label _]
[react/view {:style (style/photo-container size)}
[fast-image/fast-image
{:source photo-path
{:source {:uri photo-path}
:style (style/photo size)
:accessibility-label (or accessibility-label :chat-icon)}]
[react/view {:style (style/photo-border size)}]])))
;; "(colors/dark?)" is passed to memoized function to avoid previous themes cache
;; TODO: it's only used for old code, `quo/user-avatar` should be used instead for all the new one
(defn photo
^:deprecated
[photo-path {:keys [size accessibility-label]}]
[memo-photo-rend photo-path size accessibility-label (colors/dark?)])
;; We optionally pass identicon for perfomance reason, so it does not have to be calculated for each
;; message
(defn member-photo
([pub-key]
(member-photo pub-key nil))
([pub-key identicon]
(member-photo pub-key identicon style/default-size))
([pub-key identicon size]
(let [path @(re-frame/subscribe [:chats/photo-path pub-key identicon])]
[photo path
{:size size
:accessibility-label :member-photo}])))
(defn account-photo
[account]
(let [path (multiaccounts/displayed-photo account)]
[photo path
{:size style/default-size
:accessibility-label :own-account-photo}]))
+4 -3
View File
@@ -14,12 +14,13 @@
(defn one-to-one-chat-accents
[chat-id]
(let [{:keys [primary-name] :as contact} @(re-frame/subscribe [:contacts/contact-by-identity chat-id])]
(let [photo @(re-frame/subscribe [:chats/photo-path chat-id])
contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity chat-id])]
[react/view
[quo/list-item
{:theme :accent
:icon [chat-icon/contact-icon-contacts-tab contact]
:title primary-name
:icon [chat-icon/contact-icon-contacts-tab photo]
:title contact-name
:subtitle (i18n/label :t/view-profile)
:accessibility-label :view-chat-details-button
:chevron true
@@ -6,6 +6,7 @@
[status-im.communities.core :as communities]
[status-im2.constants :as constants]
[utils.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar]
@@ -33,7 +34,8 @@
[quo/list-item
{:title primary-name
:subtitle secondary-name
:icon [chat-icon.screen/contact-icon-contacts-tab contact]
:icon [chat-icon.screen/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]
:accessory :checkbox
:active active
:on-press (fn []
@@ -22,7 +22,8 @@
[:<>
[quo/list-item
{:theme :accent
:icon [chat-icon/contact-icon-contacts-tab member]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]
:title primary-name
:subtitle (i18n/label :t/view-profile)
:accessibility-label :view-chat-details-button
@@ -46,7 +46,8 @@
{:flex-basis 120})
:accessory (when can-manage-users?
[request-actions community-id id])
:icon [chat-icon/contact-icon-contacts-tab member]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]
:on-press #(re-frame/dispatch [:chat.ui/show-profile public-key])}]))
(defn requests-to-join
+5 -2
View File
@@ -5,6 +5,7 @@
[reagent.core :as reagent]
[status-im2.constants :as constants]
[utils.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.invite.views :as invite]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
@@ -23,7 +24,8 @@
[quo/list-item
{:title primary-name
:subtitle secondary-name
:icon [chat-icon/contact-icon-contacts-tab row]}]))
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo row)]}]))
(defn- on-toggle
[allow-new-users? checked? public-key]
@@ -57,7 +59,8 @@
[quo/list-item
{:title primary-name
:subtitle secondary-name
:icon [chat-icon/contact-icon-contacts-tab contact]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]
:on-press #(on-toggle allow-new-users? contact-selected? public-key)
:active contact-selected?
:accessory :checkbox}])))
+4 -2
View File
@@ -205,8 +205,10 @@
[_ _]
(reagent/create-class
{:should-component-update
(fn [_ [_ _] [_ new-account]]
(not (nil? new-account)))
(fn [_ [_ old-account] [_ new-account]]
(and (not (nil? new-account))
(and (not (:identicon old-account))
(nil? (:identicon new-account)))))
:reagent-render
(fn [account small-screen?]
@@ -5,6 +5,7 @@
[re-frame.core :as re-frame]
[re-frame.db]
[utils.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.multiaccounts.key-storage.core :as multiaccounts.key-storage]
[status-im.react-native.resources :as resources]
[status-im.ui.components.accordion :as accordion]
@@ -67,7 +68,8 @@
[local-topbar (i18n/label :t/choose-actions)]
[accordion/section
{:title name
:icon [chat-icon.screen/contact-icon-contacts-tab multiaccount]
:icon [chat-icon.screen/contact-icon-contacts-tab
(multiaccounts/displayed-photo multiaccount)]
:content [accordion-content]}]
[react/view
{:flex 1
@@ -17,7 +17,8 @@
(letsubs [{:keys [pubkey
processing?
compressed-key
name]}
name
identicon]}
[:intro-wizard/recovery-success]
existing-account? [:intro-wizard/recover-existing-account?]]
[react/view
@@ -43,7 +44,14 @@
[react/view
{:justify-content :center
:align-items :center
:margin-bottom 11}]
:margin-bottom 11}
[react/image
{:source {:uri identicon}
:style {:width 61
:height 61
:border-radius 30
:border-width 1
:border-color colors/black-transparent}}]]
[react/text
{:style {:text-align :center
:color colors/black
@@ -46,7 +46,8 @@
(i18n/label :t/delete-profile)]]
[quo/list-item
{:title (multiaccounts/displayed-name multiaccount)
:icon [chat-icon.screen/contact-icon-contacts-tab multiaccount]}]
:icon [chat-icon.screen/contact-icon-contacts-tab
(multiaccounts/displayed-photo multiaccount)]}]
(when keycard?
[react/view
[quo/list-header (i18n/label :t/actions)]
@@ -23,7 +23,8 @@
[react/view
[quo/list-item
{:theme :accent
:icon [chat-icon/contact-icon-contacts-tab member]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]
:title (:primary-name member)
:subtitle (i18n/label :t/view-profile)
:accessibility-label :view-chat-details-button
@@ -58,7 +59,8 @@
{:title primary-name
:subtitle secondary-name
:accessibility-label :member-item
:icon [chat-icon/contact-icon-contacts-tab member]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]
:on-press (when (not= public-key current-user-identity)
#(re-frame/dispatch [:chat.ui/show-profile public-key]))}
(when (:admin? member)
@@ -138,7 +140,8 @@
(let [contact (or @(re-frame/subscribe [:contacts/contact-by-identity from]) {:public-key from})]
[quo/list-item
{:title (multiaccounts/displayed-name contact)
:icon [chat-icon/contact-icon-contacts-tab contact]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]
:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet-old
{:content (fn []
[invitation-sheet invitation contact])}])}]))
@@ -94,16 +94,20 @@
:online-profile-photo-dot
:offline-profile-photo-dot))
(defn icon-dot-margin
[_ _]
-2)
(defn icon-dot-size
[container-size]
(/ container-size 2.4))
(defn icon-visibility-status-dot
[public-key container-size]
[public-key container-size identicon?]
(let [my-icon? (my-icon? public-key)
visibility-status-update (visibility-status-update public-key my-icon?)
size (icon-dot-size container-size)
margin -2
margin (icon-dot-margin size identicon?)
dot-color (icon-dot-color visibility-status-update)
new-ui? true]
(merge (styles/visibility-status-dot {:color dot-color
@@ -7,6 +7,7 @@
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.stateofus :as stateofus]
[utils.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
@@ -97,7 +98,8 @@
(some-> ^js @scroll-view-ref
(.scrollTo #js {:x 0 :animated true}))
(re-frame/dispatch [:wallet.recipient/address-changed name]))
:icon [chat-icon/contact-icon-contacts-tab contact]}])
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]}])
(defn empty-items
[icon title]
@@ -83,7 +83,8 @@
{:monospace true
:color :secondary}
(utils/get-shortened-checksum-address (:address contact))]
:icon [chat-icon/contact-icon-contacts-tab contact]}]
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]}]
[quo/list-item
(merge {:title (if-not contact
(i18n/label :t/wallet-choose-recipient)
@@ -3,10 +3,12 @@
[re-frame.core :as re-frame]
[status-im.router.core :as router]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon]
[status-im.utils.universal-links.core :as links]))
(deftest handle-url-test
(with-redefs [gfycat/generate-gfy (constantly "generated")]
(with-redefs [gfycat/generate-gfy (constantly "generated")
identicon/identicon (constantly "generated")]
(testing "the user is not logged in"
(testing "it stores the url for later processing"
(is (= {:db {:universal-links/url "some-url"}}
@@ -30,19 +30,19 @@
close-button-text]}]
(let [extra-action-selected? (reagent/atom false)]
(fn []
(let [{:keys [group-chat chat-id public-key color
profile-picture name]} context
id (or chat-id public-key)
display-name (or
name
(when-not group-chat
(rf/sub [:contacts/contact-name-by-identity id])))
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address
id]))
photo-path (or profile-picture
(when-not (empty? (:images contact))
(rf/sub [:chats/photo-path id])))]
(let [{:keys [group-chat chat-id public-key color profile-picture
name]} context
id (or chat-id public-key)
display-name (or
name
(when-not group-chat
(rf/sub [:contacts/contact-name-by-identity id])))
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address
id]))
photo-path (or profile-picture
(when-not (empty? (:images contact))
(rf/sub [:chats/photo-path id])))]
[rn/view
{:style {:margin-horizontal 20}
:accessibility-label accessibility-label}
+9 -14
View File
@@ -1,20 +1,15 @@
(ns status-im2.common.resources)
(def ui
{:add-new-contact (js/require "../resources/images/ui2/add-contact.png")
:desktop-how-to-pair-sign-in (js/require "../resources/images/ui2/desktop-how-to-pair-sign-in.png")
:desktop-how-to-pair-logged-in (js/require
"../resources/images/ui2/desktop-how-to-pair-logged-in.png")
:mobile-how-to-pair-sign-in (js/require "../resources/images/ui2/mobile-how-to-pair-sign-in.png")
:mobile-how-to-pair-logged-in (js/require "../resources/images/ui2/mobile-how-to-pair-logged-in.png")
:lifestyle (js/require "../resources/images/ui2/lifestyle.png")
:music (js/require "../resources/images/ui2/music.png")
:podcasts (js/require "../resources/images/ui2/podcasts.png")
:generate-keys (js/require "../resources/images/ui2/generate-keys.png")
:ethereum-address (js/require "../resources/images/ui2/ethereum-address.png")
:use-keycard (js/require "../resources/images/ui2/keycard.png")
:onboarding-illustration (js/require "../resources/images/ui2/onboarding_illustration.png")
:qr-code (js/require "../resources/images/ui2/qr-code.png")
{:add-new-contact (js/require "../resources/images/ui2/add-contact.png")
:lifestyle (js/require "../resources/images/ui2/lifestyle.png")
:music (js/require "../resources/images/ui2/music.png")
:podcasts (js/require "../resources/images/ui2/podcasts.png")
:generate-keys (js/require "../resources/images/ui2/generate-keys.png")
:ethereum-address (js/require "../resources/images/ui2/ethereum-address.png")
:use-keycard (js/require "../resources/images/ui2/keycard.png")
:onboarding-illustration (js/require "../resources/images/ui2/onboarding_illustration.png")
:qr-code (js/require "../resources/images/ui2/qr-code.png")
})
(def mock-images
@@ -17,14 +17,14 @@
(defn user-avatar-tag
[user-id]
(let [{:keys [primary-name] :as contact} (rf/sub [:contacts/contact-by-identity user-id])]
(let [contact (rf/sub [:contacts/contact-by-identity user-id])]
[quo/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style style/user-avatar-tag
:text-style style/user-avatar-tag-text}
primary-name
(:primary-name contact)
(multiaccounts/displayed-photo contact)]))
(defn- render-swipe-action
@@ -5,7 +5,6 @@
[react-native.core :as rn]
[react-native.clipboard :as clipboard]
[reagent.core :as reagent]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.utils.utils :as utils]
[status-im2.contexts.add-new-contact.style :as style]
@@ -15,8 +14,12 @@
(defn found-contact
[public-key]
(let [{:keys [primary-name compressed-key]
:as contact} (rf/sub [:contacts/contact-by-identity public-key])]
(let [{:keys [primary-name
compressed-key
identicon
images]} (rf/sub [:contacts/contact-by-identity public-key])
profile-picture (-> (or (:thumbnail images) (:large images) (first images))
(get :uri identicon))]
(when primary-name
[rn/view style/found-user
[quo/text (style/text-description)
@@ -24,7 +27,7 @@
[rn/view (style/found-user-container)
[quo/user-avatar
{:full-name primary-name
:profile-picture (multiaccounts/displayed-photo contact)
:profile-picture profile-picture
:size :small
:status-indicator? false}]
[rn/view style/found-user-text
@@ -31,4 +31,4 @@
:left -20
:right -20
:bottom 0
:height (- constants/composer-default-height constants/bottom-padding)})
:height constants/composer-default-height})
@@ -58,9 +58,9 @@
:i/arrow-up]])
(defn send-button
[{:keys [text-value] :as state} animations window-height images?]
[state animations window-height images?]
(let [btn-opacity (reanimated/use-shared-value 0)
z-index (reagent/atom (if (and (empty? @text-value) (not images?)) 0 1))]
z-index (reagent/atom 0)]
[:f> f-send-button state animations window-height images? btn-opacity z-index]))
(defn audio-button
@@ -149,12 +149,11 @@
(defn image-button
[props animations insets]
[quo/button
{:on-press #(open-photo-selector props animations insets)
:accessibility-label :open-images-button
:icon true
:type :outline
:size 32
:style {:margin-right 12}}
{:on-press #(open-photo-selector props animations insets)
:icon true
:type :outline
:size 32
:style {:margin-right 12}}
:i/image])
(defn reaction-button
@@ -177,7 +176,7 @@
:i/format])
(defn view
[props state animations window-height insets {:keys [edit images]}]
[props state animations window-height insets edit? images?]
[rn/view {:style style/actions-container}
[rn/view
{:style {:flex-direction :row
@@ -186,6 +185,6 @@
[image-button props animations insets]
[reaction-button]
[format-button]]
[:f> send-button state animations window-height images]
(when (and (not edit) (not images))
[:f> send-button state animations window-height images?]
(when (and (not edit?) (not images?))
[audio-button props state animations])])
@@ -5,17 +5,13 @@
(def ^:const bar-container-height 20)
(def ^:const input-height 32)
(def ^:const input-height (if platform/ios? 32 42))
(def ^:const actions-container-height 56)
(def ^:const bottom-padding (if platform/android? 15 0))
(def ^:const composer-default-height (+ bar-container-height input-height actions-container-height))
(def ^:const composer-default-height (+ bar-container-height input-height actions-container-height bottom-padding))
(def ^:const line-height (:line-height typography/paragraph-1))
(def ^:const multiline-minimized-height (+ input-height (if platform/ios? 18 line-height)))
(def ^:const multiline-minimized-height (+ input-height 18))
(def ^:const empty-opacity 0.7)
@@ -27,7 +23,7 @@
(def ^:const mentions-max-height 240)
(def ^:const extra-content-offset (if platform/ios? 6 -8))
(def ^:const extra-content-offset (if platform/ios? 6 0))
(def ^:const content-change-threshold 10)
@@ -37,4 +33,6 @@
(def ^:const background-threshold 0.75)
(def ^:const line-height (:line-height typography/paragraph-1))
(def ^:const max-text-size 4096)
@@ -8,7 +8,7 @@
[status-im2.contexts.chat.composer.keyboard :as kb]
[utils.number :as utils.number]
[oops.core :as oops]
[utils.debounce :as debounce]))
[utils.re-frame :as rf]))
(defn reenter-screen-effect
[{:keys [text-value saved-cursor-position maximized?]}
@@ -37,12 +37,10 @@
(js/setTimeout #(reset! lock-layout? true) 500)))
(defn kb-default-height-effect
[{:keys [kb-default-height kb-height]}]
(when (zero? @kb-default-height)
[{:keys [kb-default-height]}]
(when-not @kb-default-height
(async-storage/get-item :kb-default-height
(fn [height]
(reset! kb-default-height (utils.number/parse-int height 0))
(reset! kb-height (utils.number/parse-int height 0))))))
#(reset! kb-default-height (utils.number/parse-int % nil)))))
(defn background-effect
[{:keys [maximized?]}
@@ -65,6 +63,32 @@
(when-not images?
(reset! sending-images? false)))
(defn edit-effect
[{:keys [input-ref]}
{:keys [text-value saved-cursor-position]}
edit]
(rn/use-effect
(fn []
(let [edit-text (get-in edit [:content :text])]
(when (and edit @input-ref)
(.focus ^js @input-ref)
(.setNativeProps ^js @input-ref (clj->js {:text edit-text}))
(reset! text-value edit-text)
(reset! saved-cursor-position (count edit-text)))))
[(:message-id edit)]))
(defn reply-effect
[{:keys [input-ref]}
{:keys [container-opacity]}
reply]
(rn/use-effect
(fn []
(when reply
(reanimated/animate container-opacity 1))
(when (and reply @input-ref)
(.focus ^js @input-ref)))
[(:message-id reply)]))
(defn audio-effect
[{:keys [recording? gesture-enabled?]}
{:keys [container-opacity]}
@@ -91,7 +115,8 @@
(.remove ^js @keyboard-frame-listener))
(defn initialize
[props state animations {:keys [max-height] :as dimensions} {:keys [chat-input images reply audio]}]
[props state animations {:keys [max-height] :as dimensions} chat-input keyboard-height images? reply?
audio]
(rn/use-effect
(fn []
(maximized-effect state animations dimensions chat-input)
@@ -99,41 +124,15 @@
(layout-effect state)
(kb-default-height-effect state)
(background-effect state animations dimensions chat-input)
(images-effect props animations images)
(images-effect props animations images?)
(audio-effect state animations audio)
(empty-effect state animations images reply audio)
(kb/add-kb-listeners props state animations dimensions)
(empty-effect state animations images? reply? audio)
(kb/add-kb-listeners props state animations dimensions keyboard-height)
#(component-will-unmount props))
[max-height]))
(defn edit
[{:keys [input-ref]}
{:keys [text-value saved-cursor-position]}
{:keys [edit]}]
(rn/use-effect
(fn []
(let [edit-text (get-in edit [:content :text])]
(when (and edit @input-ref)
(.focus ^js @input-ref)
(.setNativeProps ^js @input-ref (clj->js {:text edit-text}))
(reset! text-value edit-text)
(reset! saved-cursor-position (count edit-text)))))
[(:message-id edit)]))
(defn reply
[{:keys [input-ref]}
{:keys [container-opacity]}
{:keys [reply]}]
(rn/use-effect
(fn []
(when reply
(reanimated/animate container-opacity 1))
(when (and reply @input-ref)
(.focus ^js @input-ref)))
[(:message-id reply)]))
(defn edit-mentions
[{:keys [input-ref]} {:keys [text-value cursor-position]} {:keys [input-with-mentions]}]
[{:keys [input-ref]} {:keys [text-value cursor-position]} input-with-mentions]
(rn/use-effect (fn []
(let [input-text (reduce (fn [acc item]
(str acc (second item)))
@@ -152,14 +151,14 @@
(defn update-input-mention
[{:keys [input-ref]}
{:keys [text-value]}
{:keys [input-text]}]
input-text]
(rn/use-effect
(fn []
(when (and input-text (not= @text-value input-text))
(when @input-ref
(.setNativeProps ^js @input-ref (clj->js {:text input-text})))
(reset! text-value input-text)
(debounce/debounce-and-dispatch [:mention/on-change-text input-text] 300)))
(rf/dispatch [:mention/on-change-text input-text])))
[input-text]))
(defn did-mount
@@ -56,7 +56,7 @@
(defn drag-gesture
[{:keys [input-ref] :as props}
{:keys [gesture-enabled?] :as state}
{:keys [gesture-enabled? text-value] :as state}
{:keys [height saved-height last-height opacity background-y container-opacity] :as animations}
{:keys [max-height lines] :as dimensions}
keyboard-shown]
@@ -67,10 +67,10 @@
(gesture/on-start (fn [event]
(if-not keyboard-shown
(do ; focus and end
(reset! text-value (str (oops/oget event "velocityY")))
(when (< (oops/oget event "velocityY") constants/velocity-threshold)
(reanimated/set-shared-value container-opacity 1)
(reanimated/set-shared-value last-height max-height)
(maximize state animations dimensions))
(reanimated/set-shared-value last-height max-height))
(when @input-ref
(.focus ^js @input-ref))
(reset! gesture-enabled? false))
@@ -2,6 +2,7 @@
(:require
[quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]))
(defn top-gradient-style
@@ -24,7 +25,7 @@
:style (top-gradient-style opacity z-index)})
(def bottom-gradient-style
{:height (:line-height typography/paragraph-1)
{:height (if platform/ios? (:line-height typography/paragraph-1) 32)
:position :absolute
:bottom 0
:left 0
@@ -38,3 +39,4 @@
:start {:x 0 :y 1}
:end {:x 0 :y 0}
:style bottom-gradient-style})
@@ -8,8 +8,7 @@
[status-im2.contexts.chat.composer.keyboard :as kb]
[status-im2.contexts.chat.composer.utils :as utils]
[status-im2.contexts.chat.composer.selection :as selection]
[utils.re-frame :as rf]
[utils.debounce :as debounce]))
[utils.re-frame :as rf]))
(defn focus
[{:keys [input-ref] :as props}
@@ -38,10 +37,10 @@
[{:keys [text-value focused? lock-selection? cursor-position saved-cursor-position gradient-z-index
maximized?]}
{:keys [height saved-height last-height gradient-opacity container-opacity opacity background-y]}
{:keys [content-height max-height window-height]}
{:keys [images reply]}]
(let [lines (utils/calc-lines (- @content-height constants/extra-content-offset))
min-height (utils/get-min-height lines)
{:keys [lines content-height max-height window-height]}
images
reply]
(let [min-height (utils/get-min-height lines)
reopen-height (utils/calc-reopen-height text-value min-height content-height saved-height)]
(reset! focused? false)
(rf/dispatch [:chat.ui/set-input-focused false])
@@ -62,17 +61,13 @@
(defn content-size-change
[event
{:keys [maximized? lock-layout?]}
{:keys [maximized?]}
{:keys [height saved-height opacity background-y]}
{:keys [content-height window-height max-height]}
keyboard-shown]
(when keyboard-shown
(let [event-size (oops/oget event "nativeEvent.contentSize.height")
content-size (+ event-size constants/extra-content-offset)
lines (utils/calc-lines event-size)
content-size (if (= lines 1)
constants/input-height
(if (= lines 2) constants/multiline-minimized-height content-size))
(let [content-size (+ (oops/oget event "nativeEvent.contentSize.height")
constants/extra-content-offset)
new-height (utils/bounded-val content-size constants/input-height max-height)]
(reset! content-height content-size)
(when (utils/update-height? content-size height max-height maximized?)
@@ -81,15 +76,14 @@
(when (= new-height max-height)
(reset! maximized? true)
(rf/dispatch [:chat.ui/set-input-maximized true]))
(if (utils/show-background? max-height new-height maximized?)
(if (utils/show-background? saved-height max-height new-height)
(do
(reanimated/set-shared-value background-y 0)
(reanimated/animate opacity 1))
(when (= (reanimated/get-shared-value opacity) 1)
(reanimated/animate opacity 0)
(js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300)))
(rf/dispatch [:chat.ui/set-input-content-height new-height])
(reset! lock-layout? (> lines 2)))))
(rf/dispatch [:chat.ui/set-input-content-height new-height]))))
(defn scroll
[event
@@ -119,7 +113,7 @@
(@record-reset-fn)
(reset! recording? false))
(rf/dispatch [:chat.ui/set-chat-input-text text])
(debounce/debounce-and-dispatch [:mention/on-change-text text] 300))
(rf/dispatch [:mention/on-change-text text]))
(defn selection-change
[event
@@ -142,6 +136,8 @@
(reset! selection-event nil)))))
(defn layout
[event state blur-height]
(when (utils/update-blur-height? event state blur-height)
[event
{:keys [lock-layout?]}
blur-height]
(when (utils/update-blur-height? event lock-layout? blur-height)
(reanimated/set-shared-value blur-height (oops/oget event "nativeEvent.layout.height"))))
@@ -12,11 +12,9 @@
curr-height))
(defn store-kb-height
[event {:keys [kb-default-height kb-height]} {:keys [window-height]}]
(let [height (- window-height (oops/oget event "endCoordinates.screenY"))]
(reset! kb-height height)
(when (zero? @kb-default-height)
(async-storage/set-item! :kb-default-height (str height)))))
[{:keys [kb-default-height]} keyboard-height]
(when (and (not @kb-default-height) (pos? keyboard-height))
(async-storage/set-item! :kb-default-height (str keyboard-height))))
(defn handle-emoji-kb-ios
[event
@@ -46,10 +44,10 @@
(defn add-kb-listeners
[{:keys [keyboard-show-listener keyboard-frame-listener keyboard-hide-listener input-ref] :as props}
state animations dimensions]
state animations dimensions keyboard-height]
(reset! keyboard-show-listener (.addListener rn/keyboard
"keyboardDidShow"
#(store-kb-height % state dimensions)))
#(store-kb-height state keyboard-height)))
(reset! keyboard-frame-listener (.addListener
rn/keyboard
"keyboardWillChangeFrame"
@@ -1,5 +1,6 @@
(ns status-im2.contexts.chat.composer.mentions.view
(:require
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
@@ -30,17 +31,17 @@
user])
(defn- f-view
[suggestions-atom props state animations max-height cursor-pos images reply edit]
(let [suggestions (rf/sub [:chat/mention-suggestions])
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
size (count suggestions)
data {:keyboard-height @(:kb-height state)
:insets (safe-area/get-insets)
:curr-height (reanimated/get-shared-value (:height animations))
:window-height (:height (rn/get-window))
:images images
:reply reply
:edit edit}
[suggestions-atom props state animations max-height cursor-pos]
(let [{:keys [keyboard-height]} (hooks/use-keyboard)
suggestions (rf/sub [:chat/mention-suggestions])
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
size (count suggestions)
data {:keyboard-height keyboard-height
:insets (safe-area/get-insets)
:curr-height (reanimated/get-shared-value (:height animations))
:window-height (rf/sub [:dimensions/window-height])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])}
mentions-pos (utils/calc-suggestions-position cursor-pos max-height size state data)]
(rn/use-effect
(fn []
@@ -61,6 +62,6 @@
:accessibility-label :mentions-list}]]))
(defn view
[props state animations max-height cursor-pos images reply edit]
[props state animations max-height cursor-pos]
(let [suggestions-atom (reagent/atom {})]
[:f> f-view suggestions-atom props state animations max-height cursor-pos images reply edit]))
[:f> f-view suggestions-atom props state animations max-height cursor-pos]))
@@ -79,7 +79,7 @@
(format-reply-author from contact-name current-public-key)]]))
(defn quoted-message
[{:keys [from content-type contentType parsed-text content deleted? deleted-for-me?
[{:keys [from identicon content-type contentType parsed-text content deleted? deleted-for-me?
album-images-count]}
in-chat-input? pin? recording-audio?]
(let [contact-name (rf/sub [:contacts/contact-name-by-identity from])
@@ -100,6 +100,7 @@
[rn/view {:style (style/quoted-message pin?)}
[reply-from
{:from from
:identicon identicon
:contact-name contact-name
:current-public-key current-public-key}]
[quo/text
@@ -55,23 +55,22 @@
:overflow :hidden}))
(defn input-view
[{:keys [recording?]}]
[{:keys [saved-emoji-kb-extra-height]}
{:keys [focused? recording?]}]
{:z-index 1
:position (if @saved-emoji-kb-extra-height :relative :absolute)
:top 0
:left 0
:right (when (or focused? platform/ios?) 0)
:flex 1
:display (if @recording? :none :flex)
:min-height constants/input-height})
(defn input-text
[{:keys [saved-emoji-kb-extra-height]}
{:keys [focused? maximized?]}]
[]
(merge typography/paragraph-1
{:color (colors/theme-colors :black :white)
:text-align-vertical :top
:position (if @saved-emoji-kb-extra-height :relative :absolute)
:top 0
:left 0
:right (when (or focused? platform/ios?) 0)
:padding-bottom (when @maximized? 0)}))
:text-align-vertical :top}))
(defn background
[opacity background-y window-height]
(reanimated/apply-animations-to-style
@@ -1,15 +1,9 @@
(ns status-im2.contexts.chat.composer.sub-view
(:require
[quo2.core :as quo]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im2.contexts.chat.composer.style :as style]
[status-im2.contexts.chat.composer.utils :as utils]
[status-im2.contexts.chat.messages.list.view :as messages.list]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[status-im2.contexts.chat.composer.style :as style]))
(defn bar
[]
@@ -20,40 +14,6 @@
[layout-height focused?]
[reanimated/view {:style (style/blur-container layout-height focused?)}
[blur/view (style/blur-view)]])
(defn blur-view
[layout-height focused?]
[:f> f-blur-view layout-height focused?])
(defn- f-shell-button
[{:keys [maximized?]} {:keys [height]} {:keys [images reply edit]}]
(let [insets (safe-area/get-insets)
extra-height (utils/calc-extra-content-height images reply edit)
translate-y (reanimated/use-shared-value
(utils/calc-shell-neg-y insets maximized? extra-height))]
(rn/use-effect (fn []
(let [extra-height (utils/calc-extra-content-height images reply edit)]
(reanimated/animate translate-y
(utils/calc-shell-neg-y insets maximized? extra-height))))
[@maximized? images reply edit])
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:bottom height ; we use height of the input directly as bottom position
:transform [{:translate-y translate-y}]} ; translate down when maximized
{:position :absolute
:left 0
:right 0})}
[quo/floating-shell-button
(merge {:jump-to
{:on-press (fn []
(rf/dispatch [:chat/close true])
(rf/dispatch [:shell/navigate-to-jump-to]))
:label (i18n/label :t/jump-to)
:style {:align-self :center}}}
(when @messages.list/show-floating-scroll-down-button
{:scroll-to-bottom {:on-press messages.list/scroll-to-bottom}}))
{}]]))
(defn shell-button
[state animations subs]
[:f> f-shell-button state animations subs])
@@ -50,14 +50,13 @@
(and (not-empty @text-value) (not @focused?) (> lines 2)))
(defn show-background?
[max-height new-height maximized?]
(or @maximized?
[saved-height max-height new-height]
(or (= (reanimated/get-shared-value saved-height) max-height)
(> new-height (* constants/background-threshold max-height))))
(defn update-blur-height?
[event {:keys [lock-layout? focused?]} layout-height]
[event lock-layout? layout-height]
(or (not @lock-layout?)
(not @focused?)
(> (reanimated/get-shared-value layout-height) (oops/oget event "nativeEvent.layout.height"))))
(defn calc-lines
@@ -65,22 +64,17 @@
(let [lines (Math/round (/ height constants/line-height))]
(if platform/ios? lines (dec lines))))
(defn calc-extra-content-height
[images? reply? edit?]
(let [height (if images? constants/images-container-height 0)
height (if reply? (+ height constants/reply-container-height) height)
height (if edit? (+ height constants/edit-container-height) height)]
height))
(defn calc-max-height
[{:keys [images reply edit]} window-height kb-height insets]
[window-height kb-height insets images? reply? edit?]
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))
max-height (- window-height
margin-top
kb-height
constants/bar-container-height
constants/actions-container-height)
max-height (- max-height (calc-extra-content-height images reply edit))]
max-height (if images? (- max-height constants/images-container-height) max-height)
max-height (if reply? (- max-height constants/reply-container-height) max-height)
max-height (if edit? (- max-height constants/edit-container-height) max-height)]
max-height))
(defn empty-input?
@@ -112,25 +106,18 @@
sub-text-lines-in-view (- sub-text-lines scrolled-lines)]
(* sub-text-lines-in-view constants/line-height)))
(defn calc-shell-neg-y
[insets maximized? extra-height]
(let [padding 6
neg-y (if @maximized? -50 0)]
(- (+ constants/bar-container-height
constants/actions-container-height
constants/bottom-padding
(:bottom insets)
padding
extra-height
neg-y))))
(defn calc-suggestions-position
[cursor-pos max-height size
{:keys [maximized?]}
{:keys [insets curr-height window-height keyboard-height images reply edit]}]
{:keys [insets curr-height window-height keyboard-height edit reply]}]
(let [base (+ constants/composer-default-height (:bottom insets) 8)
base (+ base (- curr-height constants/input-height))
base (+ base (calc-extra-content-height images reply edit))
base (if edit
(+ base constants/edit-container-height)
base)
base (if reply
(+ base constants/reply-container-height)
base)
view-height (- window-height keyboard-height (:top insets))
container-height (bounded-val
(* (/ constants/mentions-max-height 4) size)
@@ -166,8 +153,7 @@
:cursor-position (reagent/atom 0)
:saved-cursor-position (reagent/atom 0)
:gradient-z-index (reagent/atom 0)
:kb-default-height (reagent/atom 0)
:kb-height (reagent/atom 0)
:kb-default-height (reagent/atom nil)
:gesture-enabled? (reagent/atom true)
:lock-selection? (reagent/atom true)
:focused? (reagent/atom false)
@@ -177,21 +163,8 @@
:recording? (reagent/atom false)
:first-level? (reagent/atom true)
:menu-items (reagent/atom selection/first-level-menu-items)})
(defn init-subs
[]
(let [chat-input (rf/sub [:chats/current-chat-input])]
{:images (seq (rf/sub [:chats/sending-image]))
:audio (rf/sub [:chats/sending-audio])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])
:input-with-mentions (rf/sub [:chat/input-with-mentions])
:input-text (:input-text chat-input)
:input-content-height (:input-content-height chat-input)}))
(defn init-animations
[{:keys [input-text images reply audio]}
lines content-height max-height opacity background-y]
[lines input-text images reply audio content-height max-height opacity background-y]
(let [initial-height (if (> lines 1)
constants/multiline-minimized-height
constants/input-height)]
+57 -45
View File
@@ -4,10 +4,10 @@
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[status-im2.contexts.chat.composer.style :as style]
[status-im2.contexts.chat.composer.images.view :as images]
[status-im2.contexts.chat.composer.reply.view :as reply]
@@ -16,6 +16,7 @@
[status-im2.contexts.chat.composer.utils :as utils]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.actions.view :as actions]
[status-im2.contexts.chat.composer.keyboard :as kb]
[status-im2.contexts.chat.composer.sub-view :as sub-view]
[status-im2.contexts.chat.composer.effects :as effects]
[status-im2.contexts.chat.composer.gesture :as drag-gesture]
@@ -25,46 +26,60 @@
(defn sheet-component
[{:keys [insets window-height blur-height opacity background-y]} props state]
(let [subs (utils/init-subs)
content-height (reagent/atom (or (:input-content-height subs)
constants/input-height))
{:keys [keyboard-shown]} (hooks/use-keyboard)
max-height (utils/calc-max-height subs
window-height
@(:kb-height state)
insets)
lines (utils/calc-lines (- @content-height
constants/extra-content-offset))
max-lines (utils/calc-lines max-height)
animations (utils/init-animations
subs
lines
content-height
max-height
opacity
background-y)
dimensions {:content-height content-height
:max-height max-height
:window-height window-height
:lines lines
:max-lines max-lines}
show-bottom-gradient? (utils/show-bottom-gradient? state dimensions)
cursor-pos (utils/cursor-y-position-relative-to-container props
state)]
(let [images (rf/sub [:chats/sending-image])
audio (rf/sub [:chats/sending-audio])
reply (rf/sub [:chats/reply-message])
edit (rf/sub [:chats/edit-message])
input-with-mentions (rf/sub [:chat/input-with-mentions])
{:keys [input-text input-content-height]
:as chat-input} (rf/sub [:chats/current-chat-input])
content-height (reagent/atom (or input-content-height
constants/input-height))
{:keys [keyboard-shown keyboard-height]} (hooks/use-keyboard)
kb-height (kb/get-kb-height keyboard-height
@(:kb-default-height state))
max-height (utils/calc-max-height window-height
kb-height
insets
(boolean (seq images))
reply
edit)
lines (utils/calc-lines @content-height)
max-lines (utils/calc-lines max-height)
animations (utils/init-animations
lines
input-text
images
reply
audio
content-height
max-height
opacity
background-y)
dimensions {:content-height content-height
:max-height max-height
:window-height window-height
:lines lines
:max-lines max-lines}
show-bottom-gradient? (utils/show-bottom-gradient? state dimensions)
cursor-pos (utils/cursor-y-position-relative-to-container props
state)]
(effects/did-mount props)
(effects/initialize props
state
animations
dimensions
subs)
(effects/edit props state subs)
(effects/reply props animations subs)
(effects/update-input-mention props state subs)
(effects/edit-mentions props state subs)
chat-input
keyboard-height
(boolean (seq images))
reply
audio)
(effects/edit-effect props state edit)
(effects/reply-effect props animations reply)
(effects/update-input-mention props state input-text)
(effects/edit-mentions props state input-with-mentions)
[:<>
[sub-view/shell-button state animations subs]
[mentions/view props state animations max-height cursor-pos (:images subs) (:reply subs)
(:edit subs)]
[mentions/view props state animations max-height cursor-pos]
[gesture/gesture-detector
{:gesture (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)}
[reanimated/view
@@ -81,17 +96,17 @@
[rn/selectable-text-input
{:ref #(reset! (:selectable-input-ref props) %)
:menu-items @(:menu-items state)
:style (style/input-view state)}
:style (style/input-view props state)}
[rn/text-input
{:ref #(reset! (:input-ref props) %)
:default-value @(:text-value state)
:on-focus #(handler/focus props state animations dimensions)
:on-blur #(handler/blur state animations dimensions subs)
:on-blur #(handler/blur state animations dimensions images reply)
:on-content-size-change #(handler/content-size-change %
state
animations
dimensions
(or keyboard-shown (:edit subs)))
(or keyboard-shown edit))
:on-scroll #(handler/scroll % props state animations dimensions)
:on-change-text #(handler/change-text % props state)
:on-selection-change #(handler/selection-change % props state)
@@ -101,20 +116,17 @@
:multiline true
:placeholder (i18n/label :t/type-something)
:placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50)
:style (style/input-text props state)
:style (style/input-text)
:max-length constants/max-text-size
:accessibility-label :chat-message-input}]]
[gradients/view props state animations show-bottom-gradient?]]
[images/images-list]
[actions/view props state animations window-height insets subs]
(when (and platform/android? (not @(:focused? state)))
[rn/view {:style {:height constants/bottom-padding
:background-color :transparent}}])
]]]))
[actions/view props state animations window-height insets edit
(boolean (seq images))]]]]))
(defn composer
[insets]
(let [window-height (:height (rn/get-window))
(let [window-height (rf/sub [:dimensions/window-height])
opacity (reanimated/use-shared-value 0)
background-y (reanimated/use-shared-value (- window-height))
blur-height (reanimated/use-shared-value (+ constants/composer-default-height
+1 -3
View File
@@ -102,8 +102,7 @@
[]
(fn []
(let [pending-contact-requests (rf/sub [:activity-center/pending-contact-requests])
selected-tab (or (rf/sub [:messages-home/selected-tab])
:tab/recent)
selected-tab (or (rf/sub [:messages-home/selected-tab]) :tab/recent)
top (safe-area/get-top)]
[:<>
(if (= selected-tab :tab/contacts)
@@ -123,7 +122,6 @@
[quo/discover-card
{:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}]
^{:key (str "tabs-" selected-tab)}
[quo/tabs
{:style style/tabs
:size 32
@@ -9,10 +9,8 @@
contact (rf/sub [:contacts/contact-by-address public-key])
photo-path (when (seq (:images contact)) (rf/sub [:chats/photo-path public-key]))
online? (rf/sub [:visibility-status-updates/online? public-key])]
[rn/view {:style {:padding-top 2}}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:chat.ui/show-profile public-key])}
[rn/touchable-without-feedback {:on-press #(rf/dispatch [:chat.ui/show-profile public-key])}
[rn/view {:padding-top 2}
[quo/user-avatar
{:full-name display-name
:profile-picture photo-path

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