Compare commits

..
Author SHA1 Message Date
Yevheniia Berdnyk 068e47bb79 Android 11 - test 2023-04-13 02:59:43 +03:00
360 changed files with 4173 additions and 7387 deletions
-1
View File
@@ -7,7 +7,6 @@
# Xcode
#
/ios/.xcode.env.local
/component-spec
result/
build/
-11
View File
@@ -282,17 +282,6 @@ else
npx react-native run-ios
endif
show-ios-devices: export TARGET := ios
show-ios-devices: ##@other shows connected ios device and its name
xcrun xctrace list devices
run-ios-device: export TARGET := ios
run-ios-device: ##@run iOS app and start it on a connected device by its name
ifndef DEVICE_NAME
$(error Usage: make run-ios-device DEVICE_NAME=your-device-name)
endif
react-native run-ios --device "$(DEVICE_NAME)"
#--------------
# Tests
#--------------
+1 -4
View File
@@ -32,10 +32,7 @@
android:theme="@style/Theme.AppSplash"
android:name=".MainApplication"
android:largeHeap="true"
android:usesCleartextTraffic="true"
android:extractNativeLibs="true">
<!-- After upgrading Android Gradle Plugin to 4.2.0 and above we must get rid of `extractNativeLibs="true"`
and use`useLegacyPackaging` flag in our app's `build.gradle`-->
android:usesCleartextTraffic="true">
<meta-data android:name="commitHash" android:value="${commitHash}"/>
<activity
android:name=".MainActivity"
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
/* 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.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
agent { label 'linux' }
+2 -2
View File
@@ -1,10 +1,10 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
pipeline {
agent { label 'macos && arm64 && nix-2.11 && xcode-14.3' }
agent { label 'macos && arm64 && nix-2.11 && xcode-14.2' }
parameters {
string(
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
/* 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.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.2'
library 'status-jenkins-lib@v1.6.9'
pipeline {
agent {
+3 -3
View File
@@ -23,13 +23,13 @@ setups and runs the test suite once.
setups and runs the test suite and watches for code changes will then retrigger the test suite.
## Writing Tests
New test files will need their namespace added to either the file "src/quo2/core_spec.cljs" or "src/status_im2/core_spec.cljs. These locations may update overtime but it is dependent on the entrypoint in shadow-cljs config discussed below.
New test files will need their namespace added to either the file "src/quo2/core_spec.cljs" or "src/status_im2/core_spec.cljs. These locations may update overtime but it is dependent on the entrypoint in shadowcljs config discussed below.
### Best practices
For the moment we will keep best practices for tests in our other guidelines document:
To that point these guidelines will follow the conventions of Jest and React Native Testing Library recommendations and Status mobile will just stack their preferences on top.
To that point these guidelines will follow the conventions of Jest and React Native Testing Library recomendations and Status mobile will just stack their preferences on top.
### Utilities
There is a file of utility functions defined in "src/test_helpers/component.cljs" and "src/test_helpers/component.clj". It will be great to use these utilities and to add any common testing tools to these files as it should make writing tests easier and faster.
@@ -52,4 +52,4 @@ It's worth knowing that our tests are compiled to JS and then run in the tempora
### Jest
There is also further configuration for Jest in "test/jest". There is a jest config file which has some mostly standard configuration pieces, where the tests live, what environment variables are set etc. This is documented by Jest here: https://jestjs.io/docs/configuration
There is also a setup file which is used to set some global and default values. Additionally this file is used to mock some of the react native (among other) dependencies
There is also a setup file which is used to set some global and default values. Additionally this file is used to mock some of the react native (among other) dependencies
+4 -75
View File
@@ -36,50 +36,6 @@ Pay special attention to:
## Dos and don'ts
### Hiccup
Never use anonymous inline function in hiccup, this will lead to reinitialization of component on each render of parent component
```clojure
;; bad
(defn checkbox-view
[{:keys [size]}]
[rn/view
[(fn [] [rn/view])]])
;; good
(defn comp []
[rn/view])
(defn checkbox-view
[{:keys [size]}]
[rn/view
[comp]])
```
This mistake mostly happens with functional components
```clojure
;; bad
(fn []
(let [atom (rf/sub [:sub])]
(fn []
[:f>
(fn []
[rn/text atom]
;; good
(defn f-comp [atom]
[rn/text atom])
(fn []
(let [atom (rf/sub [:sub])]
(fn []
[:f> f-comp atom])))
```
it's important to name functional components with `f-` prefix
### Component styles
Prefer to define styles in a separate file named `style.cljs`, colocated with
@@ -134,38 +90,11 @@ their styles in the top-level of the properties map, prefer to add them inside t
]
```
Also its fine to keep one liner styles in view
```clojure
;; ok
[rn/view {:style {:flex 1 :padding-top 5}}]
```
### Don't define properties in styles ns
Properties must be set on view level
```clojure
;; bad
{:style {:position :absolute
:left 0
:right 0
:bottom 0}
:blur-amount 30
:blur-radius 25
:blur-type :transparent
:overlay-color :transparent}
;; good
{:position :absolute
:left 0
:right 0
:bottom 0}
```
### Apply animated styles in the style file
### Always apply animated styles in the style file
When implementing styles for reanimated views, we should always define
them in the style file and apply animations with reanimated/apply-animations-to-style
in the style definition.
```clojure
;; bad
+9 -9
View File
@@ -256,6 +256,8 @@ PODS:
- React-Core
- react-native-randombytes (3.6.1):
- React-Core
- react-native-safe-area-context (2.0.0):
- React
- react-native-shake (3.4.0):
- React
- react-native-slider (3.0.0):
@@ -410,8 +412,6 @@ PODS:
- Yoga
- RNShare (7.0.1):
- React-Core
- RNStaticSafeAreaInsets (2.2.0):
- React-Core
- RNSVG (9.13.6):
- React
- SDWebImage (5.11.1):
@@ -468,6 +468,7 @@ DEPENDENCIES:
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-shake (from `../node_modules/react-native-shake`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-status (from `../modules/react-native-status`)
@@ -502,7 +503,6 @@ DEPENDENCIES:
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNShare (from `../node_modules/react-native-share`)
- RNStaticSafeAreaInsets (from `../node_modules/react-native-static-safe-area-insets`)
- RNSVG (from `../node_modules/react-native-svg`)
- secp256k1 (from `https://github.com/status-im/secp256k1.swift.git`)
- SQLCipher (~> 3.0)
@@ -589,6 +589,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-orientation-locker"
react-native-randombytes:
:path: "../node_modules/react-native-randombytes"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-shake:
:path: "../node_modules/react-native-shake"
react-native-slider:
@@ -657,8 +659,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-reanimated"
RNShare:
:path: "../node_modules/react-native-share"
RNStaticSafeAreaInsets:
:path: "../node_modules/react-native-static-safe-area-insets"
RNSVG:
:path: "../node_modules/react-native-svg"
secp256k1:
@@ -682,11 +682,11 @@ SPEC CHECKSUMS:
boost: a7c83b31436843459a1961bfd74b96033dc77234
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CryptoSwift: c4f2debceb38bf44c80659afe009f71e23e4a082
DoubleConversion: cde416483dac037923206447da6e1454df403714
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
FBLazyVector: d2db9d00883282819d03bbd401b2ad4360d47580
FBReactNativeSpec: 94da4d84ba3b1acf459103320882daa481a2b62d
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 997518ea2aa2d8cd5df9797b641b758d52ecf2bc
glog: 9e3310013355e9221591364060e841c28041dfe3
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
Keycard: ac6df4d91525c3c82635ac24d4ddd9a80aca5fc8
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
@@ -716,6 +716,7 @@ SPEC CHECKSUMS:
react-native-netinfo: ddaca8bbb9e6e914b1a23787ccb879bc642931c9
react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-safe-area-context: 60f654e00b6cc416573f6d5dbfce3839958eb57a
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
react-native-slider: 12bd76d3d568c9c5500825db54123d44b48e4ad4
react-native-status: 21f75d492fd311dc111303da38a7a2b23a8a8466
@@ -750,7 +751,6 @@ SPEC CHECKSUMS:
RNReactNativeHapticFeedback: 2566b468cc8d0e7bb2f84b23adc0f4614594d071
RNReanimated: 3ad6ec4e147462206be9d1c925df10b6ea850b0e
RNShare: 2dc2fcac3f7321cfd6b60a23ed4bf4d549f86f5f
RNStaticSafeAreaInsets: 055ddbf5e476321720457cdaeec0ff2ba40ec1b8
RNSVG: 8ba35cbeb385a52fd960fd28db9d7d18b4c2974f
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
@@ -763,4 +763,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: c29de3b14e3275299c51aa95520622f09d084bcb
COCOAPODS: 1.12.1
COCOAPODS: 1.12.0
+9 -5
View File
@@ -417,7 +417,9 @@
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = 8B5X2M6H2Y;
LastSwiftMigration = 1140;
ProvisioningStyle = Manual;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
@@ -542,7 +544,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = "/usr/bin/env sh";
shellScript = "set -o errexit\nexport NODE_BINARY=\"${NODE_BINARY:-node}\"\nexport NODE_ARGS=\"${NODE_ARGS:- --max-old-space-size=16384 }\"\n\nbash -x ../node_modules/react-native/scripts/react-native-xcode.sh > ./logs/react-native-xcode.log 2>&1";
shellScript = "set -o errexit\nexport NODE_BINARY=\"${NODE_BINARY:-node}\"\nexport NODE_ARGS=\" --openssl-legacy-provider --max-old-space-size=16384 \"\n\nbash -x ../node_modules/react-native/scripts/react-native-xcode.sh > ./logs/react-native-xcode.log 2>&1";
};
3AAD2AD524A3A60E0075D594 /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
@@ -556,7 +558,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = "/usr/bin/env sh";
shellScript = "set -o errexit\nexport NODE_BINARY=\"${NODE_BINARY:-node}\"\nexport NODE_ARGS=\"${NODE_ARGS:- --max-old-space-size=16384 }\"\n\nbash -x ../node_modules/react-native/scripts/react-native-xcode.sh > ./logs/react-native-xcode.log 2>&1";
shellScript = "set -o errexit\nexport NODE_BINARY=\"${NODE_BINARY:-node}\"\nexport NODE_ARGS=\" --openssl-legacy-provider --max-old-space-size=16384 \"\n\nbash -x ../node_modules/react-native/scripts/react-native-xcode.sh > ./logs/react-native-xcode.log 2>&1";
};
3AAD2AD724A3A60E0075D594 /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
@@ -841,8 +843,9 @@
BUNDLE_ID_SUFFIX = .debug;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StatusIm/StatusIm.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_IDENTITY = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CUSTOM_PRODUCT_NAME = "Status Debug";
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8B5X2M6H2Y;
@@ -896,7 +899,8 @@
PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum;
PRODUCT_NAME = StatusIm;
PROVISIONING_PROFILE_SPECIFIER = "";
PROVISIONING_PROFILE = "9da75626-9594-43d9-a827-0f6d43c28f54";
PROVISIONING_PROFILE_SPECIFIER = "match Development im.status.ethereum";
SWIFT_OBJC_BRIDGING_HEADER = "StatusIm-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
-13
View File
@@ -15,7 +15,6 @@ stdenv.mkDerivation {
"patchYogaNodePackagePhase"
"patchReactNativePhase"
"patchPodPhase"
"patchReactNativeXcodeScriptPhase"
"installPhase"
];
@@ -107,18 +106,6 @@ stdenv.mkDerivation {
'[RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil];'
'';
# Patch React Native Xcode Script that searches for nvm
# FIXME: Remove this once we upgrade react-native to 0.69.x
patchReactNativeXcodeScriptPhase = ''
substituteInPlace ./node_modules/react-native/scripts/find-node.sh --replace \
'# Define NVM_DIR and source the nvm.sh setup script' \
'<<MULTI_LINE_COMMENT${"\n"}# Define NVM_DIR and source the nvm.sh setup script'
substituteInPlace ./node_modules/react-native/scripts/find-node.sh --replace \
'# Set up the nodenv node version manager if present' \
'MULTI_LINE_COMMENT${"\n"}# Set up the nodenv node version manager if present'
'';
# The ELF types are incompatible with the host platform, so let's not even try
# TODO: Use Android NDK to strip binaries manually
+1 -5
View File
@@ -1,9 +1,6 @@
{ callPackage, lib, mkShell, pkgs, stdenv
{ callPackage, lib, mkShell, pkgs
, status-go, fastlane }:
assert lib.assertMsg stdenv.isDarwin
"iOS development shell supported only on OSX.";
let
inherit (lib) catAttrs unique;
@@ -20,7 +17,6 @@ in {
buildInputs = with pkgs; [
xcodeWrapper watchman procps
flock # used in nix/scripts/node_modules.sh
ios-deploy # used in 'make run-ios-device'
];
# WARNING: Executes shellHook in reverse order.
+3 -3
View File
@@ -1,15 +1,15 @@
{ mkShell, nodejs, deps }:
mkShell {
# Check if node modules changed and if so install them.
shellHook = ''
# Fix for ERR_OSSL_EVP_UNSUPPORTED error.
export NODE_OPTIONS="--openssl-legacy-provider";
# Same fix but for Xcode React Native script.
export NODE_ARGS="--openssl-legacy-provider --max-old-space-size=16384";
# Fix Xcode using system Node.js version.
export NODE_BINARY="${nodejs}/bin/node";
# Check if node modules changed and if so install them.
export STATUS_MOBILE_HOME=$(git rev-parse --show-toplevel)
"$STATUS_MOBILE_HOME/nix/scripts/node_modules.sh" ${deps.nodejs-patched}
'';
}
+1 -13
View File
@@ -44,25 +44,13 @@ in {
});
};
# Fix for missing libarclite_macosx.a in Xcode 14.3.
# https://github.com/ios-control/ios-deploy/issues/580
ios-deploy = super.darwin.ios-deploy.overrideAttrs (old: rec {
version = "1.12.2";
src = super.fetchFromGitHub {
owner = "ios-control";
repo = "ios-deploy";
rev = version;
sha256 = "sha256-TVGC+f+1ow3b93CK3PhIL70le5SZxxb2ug5OkIg8XCA";
};
});
# Package version adjustments
gradle = super.gradle_6;
nodejs = super.nodejs-18_x;
yarn = super.yarn.override { nodejs = super.nodejs-18_x; };
openjdk = super.openjdk8_headless;
xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } {
version = "14.0";
version = "13.3";
allowHigher = true;
};
go = super.go_1_19;
+6 -3
View File
@@ -12,19 +12,22 @@ let
# the default shell with most commonly used tools
default = callPackage ./shell.nix { };
# Combines with many other shells
nodejs-sh = targets.mobile.ios.nodejs-sh;
# An attrset for easier merging with default shell
shells = {
inherit default;
nodejs = nodejs-sh;
# for calling clojure targets in CI or Makefile
clojure = mkShell {
buildInputs = with pkgs; [ clojure flock maven openjdk ];
inputsFrom = [ nodejs-sh ];
# CLASSPATH from clojure deps with 'src' appended to find local sources.
shellHook = with pkgs; ''
export CLASS_PATH="$(find ${deps.clojure} -iname '*.jar' | tr '\n' ':')src"
# Check if node modules changed and if so install them.
"$STATUS_MOBILE_HOME/nix/scripts/node_modules.sh" ${pkgs.deps.nodejs-patched}
'';
};
+1 -1
View File
@@ -63,9 +63,9 @@
"react-native-randombytes": "^3.6.1",
"react-native-reanimated": "2.3.3",
"react-native-redash": "^16.0.11",
"react-native-safe-area-context": "^2.0.0",
"react-native-shake": "^3.3.1",
"react-native-share": "^7.0.1",
"react-native-static-safe-area-insets": "^2.2.0",
"react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#refs/tags/v2.5.39",
"react-native-svg": "^9.8.4",
"react-native-touch-id": "^4.4.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 913 B

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 869 B

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 986 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 539 KiB

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 KiB

+5 -3
View File
@@ -141,7 +141,6 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
{:ClipPath #js {:render identity}
:Circle #js {:render identity}
:Defs #js {:render identity}
:G #js {:render identity}
:Path #js {:render identity}
:Rect #js {:render identity}
:SvgUri #js {:render identity}
@@ -155,7 +154,10 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
(def net-info #js {})
(def touchid #js {})
(def react-native-image-viewing #js {:default {}})
(def react-native-static-safe-area-insets #js {:default {}})
(def safe-area-context
(clj->js {:SafeAreaProvider {:_reactNativeIphoneXHelper {:getStatusBarHeight (fn [])}}
:SafeAreaInsetsContext {:Consumer (fn [])}
:SafeAreaView {}}))
(def back-handler
#js
@@ -364,7 +366,7 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
"react-native-background-timer" background-timer
"react-native-image-crop-picker" image-crop-picker
"react-native-gesture-handler" react-native-gesture-handler
"react-native-static-safe-area-insets" react-native-static-safe-area-insets
"react-native-safe-area-context" safe-area-context
"react-native-config" config
"react-native-iphone-x-helper" (clj->js {:getStatusBarHeight (fn [])
:getBottomSpace (fn [])})
+9 -7
View File
@@ -2,10 +2,10 @@
(:require [oops.core :refer [oget]]
[quo.animated :as animated]
[quo.components.header :as header]
[quo.components.safe-area :as safe-area]
[quo.design-system.colors :as colors]
[quo.platform :as platform]
[reagent.core :as reagent]
[react-native.safe-area :as safe-area]))
[reagent.core :as reagent]))
(defn header-wrapper-style
[{:keys [value offset]}]
@@ -95,9 +95,11 @@
(defn header
[{:keys [use-insets] :as props} & children]
(if use-insets
[header-container
(-> props
(dissoc :use-insets)
(assoc :insets (safe-area/get-insets)))
children]
[safe-area/consumer
(fn [insets]
[header-container
(-> props
(dissoc :use-insets)
(assoc :insets insets))
children])]
[header-container props children]))
+3 -3
View File
@@ -2,13 +2,13 @@
(:require [cljs-bean.core :as bean]
[quo.animated :as animated]
[quo.components.bottom-sheet.style :as styles]
[quo.components.safe-area :as safe-area]
[quo.design-system.colors :as colors]
[quo.gesture-handler :as gesture-handler]
[quo.platform :as platform]
[quo.react :as react]
[quo.react-native :as rn]
[reagent.core :as reagent]
[react-native.safe-area :as safe-area]))
[reagent.core :as reagent]))
(def opacity-coeff 0.8)
(def close-duration 150)
@@ -43,7 +43,7 @@
(rn/use-keyboard)
keyboard-height-android-delta
(if (and platform/android? keyboard-shown) (+ keyboard-height 20) 0)
safe-area (safe-area/get-insets)
safe-area (safe-area/use-safe-area)
window-height (- window-height
(if platform/android?
(+ 50 keyboard-height-android-delta) ;; TODO : remove 50 when
+23
View File
@@ -0,0 +1,23 @@
(ns quo.components.safe-area
(:require ["react-native-safe-area-context" :as safe-area-context :refer
(SafeAreaView SafeAreaProvider SafeAreaInsetsContext useSafeAreaInsets)]
[reagent.core :as reagent]))
(def provider (reagent/adapt-react-class SafeAreaProvider))
(def ^:private consumer-raw (reagent/adapt-react-class (.-Consumer ^js SafeAreaInsetsContext)))
(def view (reagent/adapt-react-class SafeAreaView))
(defn consumer
[component]
[consumer-raw
(fn [insets]
(reagent/as-element
[component (js->clj insets :keywordize-keys true)]))])
(defn use-safe-area
[]
(let [insets (useSafeAreaInsets)]
{:top (.-top ^js insets)
:bottom (.-bottom ^js insets)
:left (.-left ^js insets)
:right (.-right ^js insets)}))
+4
View File
@@ -8,6 +8,7 @@
[quo.components.list.header :as list-header]
[quo.components.list.index :as list-index]
[quo.components.list.item :as list-item]
[quo.components.safe-area :as safe-area]
[quo.components.separator :as separator]
[quo.components.text :as text]
[quo.components.text-input :as text-input]
@@ -28,5 +29,8 @@
(def switch controls/switch)
(def radio controls/radio)
(def checkbox controls/checkbox)
(def safe-area-provider safe-area/provider)
(def safe-area-consumer safe-area/consumer)
(def safe-area-view safe-area/view)
(def separator separator/separator)
(def get-color colors/get-color)
+4 -4
View File
@@ -71,7 +71,7 @@
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:key-fn str}]]))
{:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:key-fn str}]]))
+4 -4
View File
@@ -79,7 +79,7 @@
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:key-fn str}]])
{:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:key-fn str}]])
+5 -5
View File
@@ -44,8 +44,8 @@
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:data all-props
:render-fn render-item
:key-fn str}]])
{:flex 1
:keyboardShouldPersistTaps :always
:data all-props
:render-fn render-item
:key-fn str}]])
+6 -6
View File
@@ -113,9 +113,9 @@
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
{:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
+6 -6
View File
@@ -91,9 +91,9 @@
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
{:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
+6 -6
View File
@@ -83,9 +83,9 @@
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
{:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
+5 -5
View File
@@ -31,8 +31,8 @@
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:data all-props
:render-fn render-item
:key-fn str}]])
{:flex 1
:keyboardShouldPersistTaps :always
:data all-props
:render-fn render-item
:key-fn str}]])
@@ -30,7 +30,7 @@
(reanimated/set-shared-value scroll-y current-y)))
(defn header
[{:keys [theme-color f-display-picture-comp cover-uri title-comp]} top-inset scroll-y]
[{:keys [theme-color display-picture-comp cover-uri title-comp]} top-inset scroll-y]
(let [input-range [0 (* threshold 0.33)]
picture-scale-down 0.4
size-animation (interpolate scroll-y input-range [80 (* 80 picture-scale-down)])
@@ -48,49 +48,50 @@
[reanimated/view {:style (style/header-bottom-part border-animation)}
[title-comp]]
[reanimated/view {:style (style/entity-picture size-animation)}
[:f> f-display-picture-comp image-animation]]]))
(defn- f-animated-header-list
[{:keys [header-comp main-comp back-button-on-press] :as params}]
(let [window-height (:height (rn/get-window))
{:keys [top bottom]} (safe-area/get-insets)
;; view height calculation is different because window height is different on iOS and Android:
view-height (if platform/ios?
(- window-height bottom)
(+ window-height top))
initial-y (if platform/ios? (- top) 0)
scroll-y (reanimated/use-shared-value initial-y)
opacity-animation (interpolate scroll-y
[(* threshold 0.33) (* threshold 0.66)]
[0 1])
translate-animation (interpolate scroll-y [(* threshold 0.66) threshold] [100 56])
title-opacity-animation (interpolate scroll-y [(* threshold 0.66) threshold] [0 1])]
[rn/view {:style (style/container-view view-height)}
[rn/touchable-opacity
{:active-opacity 1
:on-press back-button-on-press
:style (style/button-container {:left 20})}
[quo/icon :i/arrow-left {:size 20 :color (colors/theme-colors colors/black colors/white)}]]
[rn/touchable-opacity
{:active-opacity 1
:style (style/button-container {:right 20})}
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white)}]]
[reanimated/blur-view
{:blurAmount 32
:blurType :light
:overlayColor (if platform/ios? colors/white-opa-70 :transparent)
:style (style/blur-view opacity-animation)}
[reanimated/view {:style (style/header-comp translate-animation title-opacity-animation)}
[header-comp]]]
[reanimated/flat-list
{:data [nil]
:render-fn main-comp
:key-fn str
:header (reagent/as-element (header params top scroll-y))
;; TODO: https://github.com/status-im/status-mobile/issues/14924
:scroll-event-throttle 8
:on-scroll (fn [event] (scroll-handler event initial-y scroll-y))}]]))
[display-picture-comp image-animation]]]))
(defn animated-header-list
[params]
[:f> f-animated-header-list params])
[{:keys [header-comp main-comp back-button-on-press] :as parameters}]
[safe-area/consumer
(fn [insets]
(let [window-height (:height (rn/get-window))
status-bar-height (rn/status-bar-height)
bottom-inset (:bottom insets)
;; view height calculation is different because window height is different on iOS and Android:
view-height (if platform/ios?
(- window-height bottom-inset)
(+ window-height status-bar-height))
initial-y (if platform/ios? (- (:top insets)) 0)]
[:f>
(fn []
(let [scroll-y (reanimated/use-shared-value initial-y)
opacity-animation (interpolate scroll-y
[(* threshold 0.33) (* threshold 0.66)]
[0 1])
translate-animation (interpolate scroll-y [(* threshold 0.66) threshold] [100 56])
title-opacity-animation (interpolate scroll-y [(* threshold 0.66) threshold] [0 1])]
[rn/view {:style (style/container-view view-height)}
[rn/touchable-opacity
{:active-opacity 1
:on-press back-button-on-press
:style (style/button-container {:left 20})}
[quo/icon :i/arrow-left {:size 20 :color (colors/theme-colors colors/black colors/white)}]]
[rn/touchable-opacity
{:active-opacity 1
:style (style/button-container {:right 20})}
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white)}]]
[reanimated/blur-view
{:blurAmount 32
:blurType :light
:overlayColor (if platform/ios? colors/white-opa-70 :transparent)
:style (style/blur-view opacity-animation)}
[reanimated/view {:style (style/header-comp translate-animation title-opacity-animation)}
[header-comp]]]
[reanimated/flat-list
{:data [nil]
:render-fn main-comp
:key-fn str
:header (reagent/as-element (header parameters (:top insets) scroll-y))
;; TODO: https://github.com/status-im/status-mobile/issues/14924
:scroll-event-throttle 8
:on-scroll (fn [event] (scroll-handler event initial-y scroll-y))}]]))]))])
+3 -3
View File
@@ -65,10 +65,10 @@
18 ;; ~ 9 is char width, 18 is width used in Figma.
(* 9 max-line-digits font-scale))))
(defn- f-native-renderer
(defn- native-renderer
[{:keys [rows max-lines on-copy-press]
:or {max-lines ##Inf}}]
(let [font-scale (:font-scale (rn/get-window))
(let [font-scale (:font-scale (rn/use-window-dimensions))
total-rows (count rows)
number-rows-to-show (min (count rows) max-lines)
line-number-width (calc-line-number-width font-scale number-rows-to-show)
@@ -100,7 +100,7 @@
{:language language
:renderer (fn [^js/Object props]
(reagent/as-element
[:f> f-native-renderer
[:f> native-renderer
{:rows (-> props .-rows bean/->clj)
:on-copy-press #(when on-copy-press (on-copy-press children))
:max-lines max-lines}]))
+2 -2
View File
@@ -4,7 +4,7 @@
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]
[utils.number]))
[utils.number :as utils-number]))
(def themes
{:light {:default colors/primary-50
@@ -31,7 +31,7 @@
(= type :default))
colors/white
colors/neutral-100))
value (utils.number/parse-int value)
value (utils-number/parse-int value)
label (if (> value max-value)
(str max-value "+")
(str value))
@@ -1,19 +0,0 @@
(ns quo2.components.counter.step.component-spec
(:require [quo2.components.counter.step.view :as step]
[test-helpers.component :as h]))
(h/describe "step component"
(h/test "default render of step component"
(h/render [step/step {} nil])
(-> (h/expect (h/query-by-label-text :step-counter))
(h/is-truthy)))
(h/test "renders step with a string value"
(h/render [step/step {} "1"])
(-> (h/expect (h/get-by-text "1"))
(h/is-truthy)))
(h/test "renders step with an integer value"
(h/render [step/step {} 1])
(-> (h/expect (h/get-by-text "1"))
(h/is-truthy))))
@@ -1,41 +0,0 @@
(ns quo2.components.counter.step.style
(:require
[quo2.foundations.colors :as colors]))
(def container-base
{:align-items :center
:justify-content :center
:border-radius 6
:height 20})
(defn neutral-border-color
[in-blur-view? override-theme]
(if in-blur-view?
(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))
(def complete-background-color (colors/custom-color :blue 50))
(defn container
[size type in-blur-view? override-theme]
(cond-> container-base
(#{1 2} size) (assoc :width 20)
(= size 3) (assoc :width 28)
(= type :neutral)
(assoc :border-width 1
:border-color (neutral-border-color in-blur-view? override-theme))
(= type :active)
(assoc :background-color active-background-color)
(= type :complete)
(assoc :background-color complete-background-color)))
(defn text-color
([type] (text-color type nil))
([type override-theme]
(case type
(:neutral :active) (colors/theme-colors colors/neutral-100-opa-100 colors/white override-theme)
:complete colors/white)))
@@ -1,21 +0,0 @@
(ns quo2.components.counter.step.view
(:require
[quo2.components.counter.step.style :as style]
[quo2.components.markdown.text :as text]
[react-native.core :as rn]
[utils.number]))
(defn step
[{:keys [type accessibility-label override-theme in-blur-view?]} value]
(let [type (or type :neutral)
value (utils.number/parse-int value)
label (str value)
size (count label)]
[rn/view
{:accessible true
:accessibility-label (or accessibility-label :step-counter)
:style (style/container size type in-blur-view? override-theme)}
[text/text
{:weight :medium
:size :label
:style {:color (style/text-color type override-theme)}} label]]))
+89 -90
View File
@@ -43,7 +43,7 @@
(colors/alpha color 0.6))
(defn dropdown-comp
[{:keys [icon dd-height size disabled? dd-color use-border? border-color]}]
[{:keys [icon open? dd-height size disabled? dd-color use-border? border-color]}]
(let [dark? (colors/dark?)
{:keys [width height width-with-icon padding font icon-size]} (size sizes)
{:keys [padding-with-icon padding-with-no-icon]} padding
@@ -51,75 +51,74 @@
spacing (case size
:big 4
:medium 2
:small 2)
open? (reagent/atom false)]
(fn []
[rn/touchable-opacity
(cond->
{:on-press (fn []
(if (swap! open? not)
(apply-anim dd-height 120)
(apply-anim dd-height 0)))
:style (cond->
(merge
(if icon
padding-with-icon
padding-with-no-icon)
{:width (if icon
width-with-icon
width)
:height height
:border-radius (case size
:big 12
:medium 10
:small 8)
:flex-direction :row
:align-items :center
:background-color (if @open?
dd-color
(color-by-10 dd-color))})
use-border? (assoc :border-width 1
:border-color (if @open?
border-color
(color-by-10 border-color))))}
disabled? (assoc-in [:style :opacity] 0.3)
disabled? (assoc :disabled true))
(when icon
[icons/icon icon
{:no-color true
:size 20
:container-style {:margin-right spacing
:margin-top 1
:width icon-size
:height icon-size}}])
[text/text
{:size font-size
:weight :medium
:font :font-medium
:color :main} "Dropdown"]
[icons/icon
(if @open?
(if dark?
:main-icons/pullup-dark
:main-icons/pullup)
(if dark?
:main-icons/dropdown-dark
:main-icons/dropdown))
{:size 20
:no-color true
:container-style {:width (+ icon-size 3)
:border-radius 20
:margin-left (if (= :small size)
2
4)
:margin-top 1
:height (+ icon-size 4)}}]])))
:small 2)]
[rn/touchable-opacity
(cond->
{:on-press (fn []
(if (swap! open? not)
(apply-anim dd-height 120)
(apply-anim dd-height 0)))
:style (cond->
(merge
(if icon
padding-with-icon
padding-with-no-icon)
{:width (if icon
width-with-icon
width)
:height height
:border-radius (case size
:big 12
:medium 10
:small 8)
:flex-direction :row
:align-items :center
:background-color (if @open?
dd-color
(color-by-10 dd-color))})
use-border? (assoc :border-width 1
:border-color (if @open?
border-color
(color-by-10 border-color))))}
disabled? (assoc-in [:style :opacity] 0.3)
disabled? (assoc :disabled true))
(when icon
[icons/icon icon
{:no-color true
:size 20
:container-style {:margin-right spacing
:margin-top 1
:width icon-size
:height icon-size}}])
[text/text
{:size font-size
:weight :medium
:font :font-medium
:color :main} "Dropdown"]
[icons/icon
(if @open?
(if dark?
:main-icons/pullup-dark
:main-icons/pullup)
(if dark?
:main-icons/dropdown-dark
:main-icons/dropdown))
{:size 20
:no-color true
:container-style {:width (+ icon-size 3)
:border-radius 20
:margin-left (if (= :small size)
2
4)
:margin-top 1
:height (+ icon-size 4)}}]]))
(defn items-comp
[{:keys [items on-select]}]
(let [items-count (count items)]
[rn/scroll-view
{:horizontal false
{:style {:height "100%"}
:horizontal false
:nestedScrollEnabled true}
(doall
(map-indexed (fn [index item]
@@ -137,29 +136,29 @@
[text/text {:style {:text-align :center}} item]])
items))]))
(defn- f-dropdown
[{:keys [items icon text default-item on-select size disabled? border-color use-border? dd-color]}]
(let [dd-height (reanimated/use-shared-value 0)]
[rn/view {:style {:flex-grow 1}}
[dropdown-comp
{:items items
:icon icon
:disabled? disabled?
:size size
:dd-color dd-color
:text text
:border-color (colors/custom-color-by-theme border-color 50 60)
:use-border? use-border?
:default-item default-item
:dd-height dd-height}]
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:height dd-height}
{})}
[items-comp
{:items items
:on-select on-select}]]]))
(defn dropdown
[params]
[:f> f-dropdown params])
[{:keys [items icon text default-item on-select size disabled? border-color use-border? dd-color]}]
[:f>
(fn []
(let [open? (reagent/atom false)
dd-height (reanimated/use-shared-value 0)]
[rn/view {:style {:flex-grow 1}}
[dropdown-comp
{:items items
:icon icon
:disabled? disabled?
:size size
:dd-color dd-color
:text text
:border-color (colors/custom-color-by-theme border-color 50 60)
:use-border? use-border?
:default-item default-item
:open? open?
:dd-height dd-height}]
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:height dd-height}
{:height dd-height})}
[items-comp
{:items items
:on-select on-select}]]]))])
+13 -27
View File
@@ -1,43 +1,29 @@
(ns quo2.components.icon
(:require [clojure.string :as string]
[quo2.components.icons.icons :as icons]
[quo2.components.icons.svg :as icons.svg]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]))
(defn- valid-color?
[color]
(or (keyword? color)
(and (string? color)
(not (string/blank? color)))))
(defn memo-icon-fn
([icon-name] (memo-icon-fn icon-name nil))
([icon-name
{:keys [color color-2 no-color
container-style size accessibility-label]
{:keys [color container-style size
accessibility-label no-color]
:or {accessibility-label :icon}}]
(let [size (or size 20)]
^{:key icon-name}
(if-let [svg-icon (icons.svg/get-icon icon-name size)]
[svg-icon
{:size size
:color (when (valid-color? color) color)
:color-2 (when (valid-color? color-2) color-2)
:accessibility-label accessibility-label
:style container-style}]
[rn/image
{:style
(merge {:width size
:height size}
[rn/image
{:style
(merge {:width size
:height size}
(when (not no-color)
{:tint-color (if (and (string? color) (not (string/blank? color)))
color
(colors/theme-colors colors/neutral-100 colors/white))})
(when (not no-color)
{:tint-color (if (and (string? color) (not (string/blank? color)))
color
(colors/theme-colors colors/neutral-100 colors/white))})
container-style)
:accessibility-label accessibility-label
:source (icons/icon-source (str (name icon-name) size))}]))))
container-style)
:accessibility-label accessibility-label
:source (icons/icon-source (str (name icon-name) size))}])))
(def icon (memoize memo-icon-fn))
+4 -4
View File
@@ -2,9 +2,9 @@
(:require [clojure.java.io :as io]
[clojure.string :as string]))
(def ^:private icon-path "./resources/images/icons2/")
(def icon-path "./resources/images/icons2/")
(defn- require-icon
(defn require-icon
[size path]
(fn [el]
(let [s (str "." path el ".png")
@@ -15,7 +15,7 @@
(str size))]
[k `(js/require ~s)])))
(defn- get-files
(defn get-files
[path]
(->> (io/file path)
file-seq
@@ -23,7 +23,7 @@
(map #(first (string/split (.getName %) #"@")))
distinct))
(defn- get-icons
(defn get-icons
[size]
(let [path (str icon-path size "x" size "/")]
(into {} (map (require-icon size path) (get-files path)))))
+1 -1
View File
@@ -2,7 +2,7 @@
(:require-macros [quo2.components.icons.icons :as icons])
(:require [taoensso.timbre :as log]))
(def ^:private icons (icons/resolve-icons))
(def icons (icons/resolve-icons))
(defn icon-source
[icon]
-48
View File
@@ -1,48 +0,0 @@
(ns quo2.components.icons.svg
"Declare icons in this namespace when they have two possible colors, because the
ReactNative `:tint-color` prop affects all non-transparent pixels of PNGs. If
the icon has only one color, prefer a PNG.
Keep all SVG components private and expose them by name in the `icons` var."
(:require [react-native.svg :as svg]
[quo2.foundations.colors :as colors]))
(defn- container
[{:keys [size accessibility-label style]
:or {size 20}}
& children]
(into [svg/svg
{:accessibility-label accessibility-label
:style style
:width size
:height size
:view-box (str "0 0 " size " " size)
:fill :none}]
children))
(defn- clear-20
[{:keys [color color-2] :as props}]
(let [color (or color colors/neutral-100)
color-2 (or color-2 colors/white)]
[container props
[svg/path
{:d
"M3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10Z"
:fill color}]
[svg/path
{:d
"M9.15142 9.99998L7.07566 12.0757L7.9242 12.9243L9.99994 10.8485L12.0757 12.9242L12.9242 12.0757L10.8485 9.99998L12.9242 7.92421L12.0757 7.07568L9.99994 9.15145L7.92421 7.07572L7.07568 7.92425L9.15142 9.99998Z"
:fill color-2}]]))
(def ^:private icons
{:i/clear-20 clear-20})
(defn- append-to-keyword
[k & xs]
(keyword (apply str
(subs (str k) 1)
xs)))
(defn get-icon
[icon-name size]
(get icons (append-to-keyword icon-name "-" size)))
+2 -4
View File
@@ -132,9 +132,8 @@
:text text}])]]))))
(defn- password-input
[{:keys [default-shown?]
:or {default-shown? false}}]
(let [password-shown? (reagent/atom default-shown?)]
[_]
(let [password-shown? (reagent/atom false)]
(fn [props]
[base-input
(assoc props
@@ -162,7 +161,6 @@
- :label - A string to set as label for this input.
- :char-limit - A number to set a maximum char limit for this input.
- :on-char-limit-reach - Function executed each time char limit is reached or exceeded.
- :default-shown? - boolean to show password input initially
and supports the usual React Native's TextInput properties to control its behaviour:
- :value
- :default-value
@@ -1,8 +0,0 @@
(ns quo2.components.inputs.recovery-phrase.component-spec
(: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))))
@@ -1,45 +0,0 @@
(ns quo2.components.inputs.recovery-phrase.style
(:require [quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]))
(def container
{:min-height 40
:flex 1
:padding-vertical 4
:padding-horizontal 20})
(defn input
[]
(assoc (text/text-style {})
:height 32
:flex-grow 1
:padding-vertical 5
:text-align-vertical :top))
(defn placeholder-color
[input-state override-theme blur?]
(cond
(and (= input-state :focused) blur?)
(colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-20 override-theme)
(= input-state :focused) ; Not blur
(colors/theme-colors colors/neutral-30 colors/neutral-60 override-theme)
blur? ; :default & blur
(colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-30 override-theme)
:else ; :default & not blur
(colors/theme-colors colors/neutral-40 colors/neutral-50 override-theme)))
(defn cursor-color
[customization-color override-theme]
(colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
override-theme))
(defn error-word
[]
{:height 22
:padding-horizontal 20
:background-color colors/danger-50-opa-10
:color (colors/theme-colors colors/danger-50 colors/danger-60)})
@@ -1,54 +0,0 @@
(ns quo2.components.inputs.recovery-phrase.view
(:require [clojure.string :as string]
[quo2.components.inputs.recovery-phrase.style :as style]
[react-native.core :as rn]
[reagent.core :as reagent]))
(def ^:private custom-props
[:customization-color :override-theme :blur? :cursor-color :multiline :on-focus :on-blur
:placeholder-text-color :mark-errors? :error-pred :word-limit])
(defn- error-word
[text]
[rn/text {:style (style/error-word)}
text])
(defn- mark-error-words
[pred text word-limit]
(let [word-limit (or word-limit ##Inf)]
(into [:<>]
(comp (map-indexed (fn [idx word]
(if (or (pred word) (>= idx word-limit))
[error-word word]
word)))
(interpose " "))
(string/split text #" "))))
(defn recovery-phrase-input
[_ _]
(let [state (reagent/atom :default)
set-focused #(reset! state :focused)
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}
:as props}
text]
(let [extra-props (apply dissoc props custom-props)]
[rn/view {:style style/container}
[rn/text-input
(merge {:accessibility-label :recovery-phrase-input
:style (style/input)
:placeholder-text-color (style/placeholder-color @state override-theme blur?)
:cursor-color (style/cursor-color customization-color override-theme)
:multiline true
:on-focus (fn []
(set-focused)
(when on-focus (on-focus)))
:on-blur (fn []
(set-default)
(when on-blur (on-blur)))}
extra-props)
(if mark-errors?
(mark-error-words error-pred text word-limit)
text)]]))))
@@ -1,68 +0,0 @@
(ns quo2.components.inputs.search-input.style
(:require [quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]))
(defn placeholder-color
[state blur? override-theme]
(cond
(and blur? (= state :active))
(colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-20 override-theme)
blur? ; state is default
(colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-30 override-theme)
(= state :active)
(colors/theme-colors colors/neutral-30 colors/neutral-60 override-theme)
:else ; Not blur and state is default
(colors/theme-colors colors/neutral-40 colors/neutral-50 override-theme)))
(def clear-icon-container
{:justify-content :center
:align-items :center
:margin-left 8
:height 20
:width 20})
(defn clear-icon
[blur? override-theme]
(if blur?
(colors/theme-colors colors/neutral-80-opa-30 colors/white-opa-10 override-theme)
(colors/theme-colors colors/neutral-40 colors/neutral-60 override-theme)))
(defn cursor
[customization-color override-theme]
(colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
override-theme))
(def tag-separator {:width 8})
(def tag-container
{:flex-direction :row
:margin-right 8})
(def container
{:flex 1
:flex-direction :row
:align-items :center})
(def scroll-container
{:flex-direction :row
:flex 1
:height 32})
(def scroll-content
{:flex-grow 1
:align-items :center
:justify-content :flex-start})
(defn input-text
[disabled?]
(assoc (text/text-style {:size :paragraph-1
:weight :regular})
:flex 1
:padding-vertical 5
:min-width 120
:opacity (if disabled? 0.3 1)
:min-height 32))
@@ -1,89 +0,0 @@
(ns quo2.components.inputs.search-input.view
(:require [oops.core :as oops]
[quo2.components.icon :as icon]
[quo2.components.inputs.search-input.style :as style]
[react-native.core :as rn]
[reagent.core :as reagent]))
(def ^:private tag-separator [rn/view {:style style/tag-separator}])
(defn- inner-tags
[tags-coll]
(into [rn/view {:style style/tag-container}]
(interpose tag-separator)
tags-coll))
(defn- clear-button
[{:keys [on-press blur? override-theme]}]
[rn/touchable-opacity
{:style style/clear-icon-container
:on-press on-press}
[icon/icon :i/clear
{:color (style/clear-icon blur? override-theme)
:size 20}]])
(defn- handle-backspace
[event ^js/Object scroll-view-ref]
(when (and (= (oops/oget event "nativeEvent.key") "Backspace")
scroll-view-ref)
(.scrollToEnd scroll-view-ref #js {:animated false})))
(def ^:private props-to-remove
[:cursor-color :placeholder-text-color :editable :on-change-text :on-focus
:on-blur :on-clear :value :tags :disabled? :blur? :customization-color :override-theme])
(defn- add-children
[text-input children]
(if (seq children)
(into text-input children)
text-input))
(defn search-input
[{:keys [value]}]
(let [state (reagent/atom :default)
set-active #(reset! state :active)
set-default #(reset! state :default)
scroll-view-ref (atom nil)
use-value? (boolean value)]
(fn [{:keys [value tags disabled? blur? on-change-text customization-color
on-clear on-focus on-blur override-theme]
:or {customization-color :blue}
:as props}
& children]
(let [clean-props (apply dissoc props props-to-remove)]
[rn/view {:style style/container}
[rn/scroll-view
{:ref #(reset! scroll-view-ref %)
:style style/scroll-container
:content-container-style style/scroll-content
:horizontal true
:shows-horizontal-scroll-indicator false}
(when (seq tags)
[inner-tags tags])
(add-children
[rn/text-input
(cond-> {:style (style/input-text disabled?)
:cursor-color (style/cursor customization-color override-theme)
:placeholder-text-color (style/placeholder-color @state blur? override-theme)
:editable (not disabled?)
:on-key-press #(handle-backspace % @scroll-view-ref)
:on-change-text (fn [new-text]
(when on-change-text
(on-change-text new-text))
(reagent/flush))
:on-focus (fn []
(set-active)
(when on-focus (on-focus)))
:on-blur (fn []
(set-default)
(when on-blur (on-blur)))}
use-value? (assoc :value value)
(seq clean-props) (merge clean-props))]
(when-not use-value? children))]
(when (or (seq value) (seq children))
[clear-button
{:on-press on-clear
:blur? blur?
:override-theme override-theme}])]))))
@@ -1,43 +0,0 @@
(ns quo2.components.links.link-preview.component-spec
(:require [quo2.components.links.link-preview.view :as view]
[test-helpers.component :as h]))
(def props
{:title "Some title"
:description "Some description"
:link "status.im"
:thumbnail "data:image/png,whatever"})
(h/describe "Links - Link Preview"
(h/test "default render"
(h/render [view/view])
(h/is-truthy (h/query-by-label-text :link-preview))
(h/is-null (h/query-by-label-text :button-enable-preview)))
(h/test "renders with most common props"
(h/render [view/view props])
(h/is-truthy (h/query-by-text (:title props)))
(h/is-truthy (h/query-by-text (:description props)))
(h/is-truthy (h/query-by-text (:link props)))
(h/is-truthy (h/query-by-label-text :thumbnail)))
(h/test "does not render thumbnail if prop is not present"
(h/render [view/view (dissoc props :thumbnail)])
(h/is-null (h/query-by-label-text :thumbnail)))
(h/test "shows button to enable preview when preview is disabled"
(h/render [view/view
(assoc props
:enabled? false
:disabled-text "I'm disabled")])
(h/is-truthy (h/query-by-label-text :button-enable-preview))
(h/is-truthy (h/query-by-text "I'm disabled")))
(h/test "on-enable event"
(let [on-enable (h/mock-fn)]
(h/render [view/view
(assoc props
:enabled? false
:on-enable on-enable)])
(h/fire-event :press (h/get-by-label-text :button-enable-preview))
(h/was-called on-enable))))
@@ -1,43 +0,0 @@
(ns quo2.components.links.link-preview.style
(:require
[quo2.foundations.colors :as colors]))
(defn container
[preview-enabled?]
(merge {:border-width 1
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80)
:background-color (colors/theme-colors colors/white colors/neutral-80-opa-40)
:border-radius 16
:padding-horizontal 12
:padding-top 10
:padding-bottom 12}
(when-not preview-enabled?
{:height 139
:align-items :center
:justify-content :center})))
(def header-container
{:flex-direction :row
:align-items :center})
(def title
{:flex 1
:margin-bottom 2})
(defn link
[]
{:margin-top 8
:color (colors/theme-colors colors/neutral-50 colors/neutral-40)})
(defn thumbnail
[size]
{:width "100%"
:height (if (= size :large) 271 139)
:margin-top 12
:border-radius 12})
(def logo
{:margin-right 6
:width 16
:height 16
:border-radius 8})
@@ -1,77 +0,0 @@
(ns quo2.components.links.link-preview.view
(:require [quo2.components.buttons.button :as button]
[quo2.components.links.link-preview.style :as style]
[quo2.components.markdown.text :as text]
[react-native.core :as rn]))
(defn- button-disabled
[disabled-text on-enable]
[button/button
{:before :i/reveal
:size 32
:type :grey
:on-press on-enable
:accessibility-label :button-enable-preview}
disabled-text])
(defn- description-comp
[description]
[text/text
{:size :paragraph-2
:number-of-lines 3
:accessibility-label :description}
description])
(defn- link-comp
[link]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/link)
:accessibility-label :link}
link])
(defn- title-comp
[title]
[text/text
{:size :paragraph-1
:number-of-lines 1
:weight :semi-bold
:style style/title
:accessibility-label :title}
title])
(defn- thumbnail-comp
[thumbnail size]
[rn/image
{:style (style/thumbnail size)
:source (if (string? thumbnail)
{:uri thumbnail}
thumbnail)
:accessibility-label :thumbnail}])
(defn- logo-comp
[logo]
[rn/image
{:accessibility-label :logo
:source logo
:style style/logo}])
(defn view
[{:keys [title logo description link thumbnail
enabled? on-enable disabled-text
container-style thumbnail-size]
:or {enabled? true}}]
[rn/view
{:style (merge (style/container enabled?) container-style)
:accessibility-label :link-preview}
(if enabled?
[:<>
[rn/view {:style style/header-container}
[logo-comp logo]
[title-comp title]]
[description-comp description]
[link-comp link]
(when thumbnail
[thumbnail-comp thumbnail thumbnail-size])]
[button-disabled disabled-text on-enable])])
@@ -48,6 +48,10 @@
{:text-transform :lowercase
:color (colors/theme-colors colors/neutral-50 colors/neutral-40)})
(def clear-button
{:border-color colors/danger-50
:border-width 1})
(def clear-button-container
{:width 20
:height 20
@@ -39,8 +39,9 @@
:hit-slop {:top 3 :right 3 :bottom 3 :left 3}
:accessibility-label :button-clear-preview}
[icon/icon :i/clear
{:size 20
:color (colors/theme-colors colors/neutral-50 colors/neutral-60)}]])
{:size 20
:container-style style/clear-button
:color (colors/theme-colors colors/neutral-50 colors/neutral-60)}]])
(defn view
[{:keys [title body logo on-clear loading? loading-message container-style]}]
@@ -1,49 +0,0 @@
(ns quo2.components.links.url-preview-list.component-spec
(:require
[oops.core :as oops]
[quo2.components.links.url-preview-list.view :as view]
[test-helpers.component :as h]))
(def previews
(->> (range 3)
(map inc)
(mapv (fn [index]
{:title (str "Title " index)
:body (str "status.im." index)
:loading? false
:url (str "status.im." index)}))))
(h/describe "Links - URL Preview List"
(h/test "default render"
(h/render [view/view
{:data previews
:key-fn :url
:horizontal-spacing 10}])
(-> (count (h/query-all-by-label-text :url-preview))
(h/expect)
(.toEqual 3))
(-> (map #(oops/oget % "props.children")
(h/query-all-by-label-text :title))
(clj->js)
(h/expect)
(.toStrictEqual #js ["Title 1" "Title 2" "Title 3"])))
(h/test "on-clear event is individually handled by each preview"
(let [on-clear (h/mock-fn)]
(h/render [view/view
{:data previews
:key-fn :url
:on-clear on-clear}])
(h/fire-event :press (first (h/get-all-by-label-text :button-clear-preview)))
(h/fire-event :press (second (h/get-all-by-label-text :button-clear-preview)))
(h/was-called-times on-clear 2)))
(h/test "previews have separate loading states"
(h/render [view/view
{:data (assoc-in previews [1 :loading?] true)
:key-fn :url}])
(h/is-truthy (h/get-by-label-text :url-preview-loading))
(-> (count (h/query-all-by-label-text :url-preview))
(h/expect)
(.toEqual 2))))
@@ -1,8 +0,0 @@
(ns quo2.components.links.url-preview-list.style)
(def url-preview-gap 12)
(def url-preview-separator
{:width url-preview-gap
:padding-top 12
:padding-bottom 8})
@@ -1,86 +0,0 @@
(ns quo2.components.links.url-preview-list.view
(:require
[oops.core :as oops]
[quo2.components.links.url-preview-list.style :as style]
[quo2.components.links.url-preview.view :as url-preview]
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn- use-scroll-to-last-item
[flat-list-ref item-count item-width]
(rn/use-effect
(fn []
(when (and (pos? item-count) (pos? item-width))
;; We use a delay because calling `scrollToOffset` without a delay does
;; nothing while the flatlist is still rendering its children.
;; `scrollToEnd` doesn't work because it positions the item off-center
;; and there's no argument to offset it.
(let [timer-id (js/setTimeout
(fn []
(when (and @flat-list-ref (pos? item-count))
(.scrollToOffset ^js @flat-list-ref
#js
{:animated true
:offset (* (+ item-width style/url-preview-gap)
(max 0 (dec item-count)))})))
25)]
(fn []
(js/clearTimeout timer-id)))))
[item-count item-width]))
(defn- separator
[]
[rn/view {:style style/url-preview-separator}])
(defn- item-component
[{:keys [title body loading? logo]} _ _
{:keys [width on-clear loading-message container-style]}]
[url-preview/view
{:logo logo
:title title
:body body
:loading? loading?
:loading-message loading-message
:on-clear on-clear
:container-style (merge container-style {:width width})}])
(defn- calculate-width
[preview-width horizontal-spacing ^js e]
(reset! preview-width
(- (oops/oget e "nativeEvent.layout.width")
(* 2 horizontal-spacing))))
(defn- f-view
[]
(let [preview-width (reagent/atom 0)
flat-list-ref (atom nil)]
(fn [{:keys [data key-fn horizontal-spacing on-clear loading-message
container-style container-style-item]}]
(use-scroll-to-last-item flat-list-ref (count data) @preview-width)
;; We need to use a wrapping view expanded to 100% instead of "flex 1",
;; otherwise `on-layout` will be triggered multiple times as the flat list
;; renders its children.
[rn/view
{:style (merge container-style {:width "100%"})
:accessibility-label :url-preview-list}
[rn/flat-list
{:ref #(reset! flat-list-ref %)
:key-fn key-fn
:on-layout #(calculate-width preview-width horizontal-spacing %)
:horizontal true
:deceleration-rate :fast
:on-scroll-to-index-failed identity
:content-container-style {:padding-horizontal horizontal-spacing}
:separator [separator]
:snap-to-interval (+ @preview-width style/url-preview-gap)
:shows-horizontal-scroll-indicator false
:data data
:render-fn item-component
:render-data {:width @preview-width
:on-clear on-clear
:loading-message loading-message
:container-style container-style-item}}]])))
(defn view
[props]
[:f> f-view props])
+11 -11
View File
@@ -30,18 +30,18 @@
(when on-press
{:on-press on-press}))
[rn/view
{:style (cond-> {:flex-direction :row
:flex-grow 0
:flex-shrink 1
:align-items :center}
icon (assoc :padding-horizontal 20))}
{:style {:flex-direction :row
:flex-grow 0
:flex-shrink 1
:padding-horizontal 20
:align-items :center}}
[rn/view
{:style (cond-> {:width 20
:height 20
:align-items :center
:justify-content :center}
icon (assoc :margin-right 12))}
(when icon [icons/icon icon {:color icon-color}])]
{:style {:width 20
:height 20
:align-items :center
:justify-content :center
:margin-right 12}}
[icons/icon icon {:color icon-color}]]
[rn/view
[text/text
{:weight :medium
+56 -55
View File
@@ -20,59 +20,61 @@
:message 144}])
;; Standlone message skeleton
(defn- f-message-skeleton
(defn message-skeleton
[]
(let [color (colors/theme-colors colors/neutral-5 colors/neutral-70)
loading-color (colors/theme-colors colors/neutral-10 colors/neutral-60)
content-width (rand-nth message-content-width)
author-width (content-width :author)
message-width (content-width :message)
{window-width :width} (rn/get-window)
translate-x (reanimated/use-shared-value (- window-width))
animated-gradient-style (reanimated/apply-animations-to-style
{:transform [{:translateX translate-x}]}
{:width window-width
:height "100%"})]
(reanimated/animate-shared-value-with-repeat translate-x window-width 1000 :linear (- 1) false)
[masked-view/masked-view
{:style {:height message-skeleton-height}
:maskElement (reagent/as-element
[rn/view
{:style {:height message-skeleton-height
:flex-direction :row
:padding-vertical 11
:background-color :transparent
:padding-left 21}}
[rn/view
{:style {:height avatar-skeleton-size
:width avatar-skeleton-size
:border-radius (/ avatar-skeleton-size 2)
:background-color color
:overflow :hidden}}]
[rn/view
{:style {:padding-left 8
:background-color :transparent}}
[rn/view
{:style {:height 8
:width author-width
:border-radius 6
:background-color color
:margin-bottom 8
:overflow :hidden}}]
[rn/view
{:style {:height 16
:width message-width
:border-radius 6
:overflow :hidden
:background-color color}}]]])}
[rn/view
{:style {:flex 1
:background-color color}}
[reanimated/linear-gradient
{:colors [color color loading-color color color]
:start {:x 0 :y 0}
:end {:x 1 :y 0}
:style animated-gradient-style}]]]))
[:f>
(fn []
(let [color (colors/theme-colors colors/neutral-5 colors/neutral-70)
loading-color (colors/theme-colors colors/neutral-10 colors/neutral-60)
content-width (rand-nth message-content-width)
author-width (content-width :author)
message-width (content-width :message)
{window-width :width} (rn/use-window-dimensions)
translate-x (reanimated/use-shared-value (- window-width))
animated-gradient-style (reanimated/apply-animations-to-style
{:transform [{:translateX translate-x}]}
{:width window-width
:height "100%"})]
(reanimated/animate-shared-value-with-repeat translate-x window-width 1000 :linear (- 1) false)
[masked-view/masked-view
{:style {:height message-skeleton-height}
:maskElement (reagent/as-element
[rn/view
{:style {:height message-skeleton-height
:flex-direction :row
:padding-vertical 11
:background-color :transparent
:padding-left 21}}
[rn/view
{:style {:height avatar-skeleton-size
:width avatar-skeleton-size
:border-radius (/ avatar-skeleton-size 2)
:background-color color
:overflow :hidden}}]
[rn/view
{:style {:padding-left 8
:background-color :transparent}}
[rn/view
{:style {:height 8
:width author-width
:border-radius 6
:background-color color
:margin-bottom 8
:overflow :hidden}}]
[rn/view
{:style {:height 16
:width message-width
:border-radius 6
:overflow :hidden
:background-color color}}]]])}
[rn/view
{:style {:flex 1
:background-color color}}
[reanimated/linear-gradient
{:colors [color color loading-color color color]
:start {:x 0 :y 0}
:end {:x 1 :y 0}
:style animated-gradient-style}]]]))])
(defn skeleton
[parent-height]
@@ -82,6 +84,5 @@
colors/white
colors/neutral-90)
:flex 1}}
(doall
(for [n (range number-of-skeletons)]
[:f> f-message-skeleton {:key n}]))]))
(for [n (range number-of-skeletons)]
[message-skeleton {:key n}])]))
+57 -55
View File
@@ -10,62 +10,64 @@
(defn author
[{:keys [primary-name secondary-name short-chat-key time-str contact? verified? untrustworthy?]}]
[rn/view {:style style/container}
[:<>
[text/text
{:weight :semi-bold
:size :paragraph-2
:number-of-lines 1
:style {:color (colors/theme-colors colors/neutral-100 colors/white)}}
primary-name]
(when (not (string/blank? secondary-name))
[:f>
(fn []
[rn/view {:style style/container}
[:<>
[text/text
{:size :paragraph-2
:number-of-lines 1
:style style/middle-dot-nickname}
middle-dot]
[text/text
{:weight :medium
{:weight :semi-bold
:size :paragraph-2
:number-of-lines 1
:style {:color (colors/theme-colors colors/neutral-60 colors/neutral-40)}}
secondary-name]])]
(when contact?
[icons/icon :main-icons2/contact
{:size 12
:no-color true
:container-style style/icon-container}])
(cond
verified?
[icons/icon :main-icons2/verified
{:size 12
:no-color true
:container-style style/icon-container}]
untrustworthy?
[icons/icon :main-icons2/untrustworthy
{:size 12
:no-color true
:container-style style/icon-container}])
(when (and (not verified?) short-chat-key)
[text/text
{:monospace true
:size :paragraph-2
:number-of-lines 1
:style style/chat-key-text}
short-chat-key])
(when (and (not verified?) time-str)
[text/text
{:monospace true
:size :paragraph-2
:number-of-lines 1
:style style/middle-dot-chat-key}
middle-dot])
(when time-str
[text/text
{:monospace true
:size :paragraph-2
:accessibility-label :message-timestamp
:number-of-lines 1
:style (style/time-text verified?)}
time-str])])
:style {:color (colors/theme-colors colors/neutral-100 colors/white)}}
primary-name]
(when (not (string/blank? secondary-name))
[:<>
[text/text
{:size :paragraph-2
:number-of-lines 1
:style style/middle-dot-nickname}
middle-dot]
[text/text
{:weight :medium
:size :paragraph-2
:number-of-lines 1
:style {:color (colors/theme-colors colors/neutral-60 colors/neutral-40)}}
secondary-name]])]
(when contact?
[icons/icon :main-icons2/contact
{:size 12
:no-color true
:container-style style/icon-container}])
(cond
verified?
[icons/icon :main-icons2/verified
{:size 12
:no-color true
:container-style style/icon-container}]
untrustworthy?
[icons/icon :main-icons2/untrustworthy
{:size 12
:no-color true
:container-style style/icon-container}])
(when (and (not verified?) short-chat-key)
[text/text
{:monospace true
:size :paragraph-2
:number-of-lines 1
:style style/chat-key-text}
short-chat-key])
(when (and (not verified?) time-str)
[text/text
{:monospace true
:size :paragraph-2
:number-of-lines 1
:style style/middle-dot-chat-key}
middle-dot])
(when time-str
[text/text
{:monospace true
:size :paragraph-2
:accessibility-label :message-timestamp
:number-of-lines 1
:style (style/time-text verified?)}
time-str])])])
@@ -172,33 +172,31 @@
:style {:color (get-color :time)}}
(utils/truncate-str (:info content) 24)])]]]])
(defn- f-system-message
[{:keys [type style non-pressable? animate-landing? labels on-long-press] :as message}]
(let [sv-color (reanimated/use-shared-value
(get-color :bg (if animate-landing? :landed :default) type))]
(when animate-landing?
(reanimated/animate-shared-value-with-delay
sv-color
(get-color :bg :default type)
0
:linear
1000))
[reanimated/touchable-opacity
{:on-press #(when-not non-pressable?
(reanimated/set-shared-value sv-color (get-color :bg :pressed type)))
:on-long-press on-long-press
:style (reanimated/apply-animations-to-style
{:background-color sv-color}
(merge
{:flex-direction :row
:flex 1
:border-radius 16
:padding-vertical 9
:padding-horizontal 11
:background-color sv-color}
style))}
[sm-render message labels]]))
(defn system-message
[message]
[:f> f-system-message message])
[{:keys [type style non-pressable? animate-landing? labels on-long-press] :as message}]
[:f>
(fn []
(let [sv-color (reanimated/use-shared-value
(get-color :bg (if animate-landing? :landed :default) type))]
(when animate-landing?
(reanimated/animate-shared-value-with-delay
sv-color
(get-color :bg :default type)
0
:linear
1000))
[reanimated/touchable-opacity
{:on-press #(when-not non-pressable?
(reanimated/set-shared-value sv-color (get-color :bg :pressed type)))
:on-long-press on-long-press
:style (reanimated/apply-animations-to-style
{:background-color sv-color}
(merge
{:flex-direction :row
:flex 1
:border-radius 16
:padding-vertical 9
:padding-horizontal 11
:background-color sv-color}
style))}
[sm-render message labels]]))])
@@ -16,7 +16,7 @@
pass-through? colors/white-opa-5
:else colors/neutral-70)))
(defn- f-bottom-nav-tab
(defn bottom-nav-tab
"[bottom-nav-tab opts]
opts
{:icon :i/communities
@@ -29,68 +29,66 @@
"
[{:keys [icon new-notifications? notification-indicator counter-label
on-press pass-through? icon-color-anim accessibility-label test-ID]}]
(let [icon-animated-style (reanimated/apply-animations-to-style
{:tint-color icon-color-anim}
{:width 24
:height 24
:margin-left 33
:margin-top 8})
background-color (reanimated/use-shared-value "transparent")
background-animated-style (reanimated/apply-animations-to-style
{:background-color background-color}
{:width 90
:height 40
:border-radius 10})]
[rn/touchable-without-feedback
{:test-ID test-ID
:on-press on-press
:on-press-in #(toggle-background-color background-color false pass-through?)
:on-press-out #(toggle-background-color background-color true pass-through?)
:accessibility-label accessibility-label}
[reanimated/view {:style background-animated-style}
;; In android animations are not working for the animated components which are nested by
;; hole-view,
;; Interestingly this only happens when hole view and blur view are used together
;; Similar behavior is also seen while removing holes, and to fix that we used key and
;; force-rendered view
;; But we need animations faster for tab clicks, so we can't rely on reagent atoms,
;; so for now only using hole view for the ios tab icon notification boundary
(if platform/ios?
[hole-view/hole-view
{:key new-notifications? ;; Key is required to force removal of holes
:holes (cond
(not new-notifications?) ;; No new notifications, remove holes
[]
[:f>
(fn []
(let [icon-animated-style (reanimated/apply-animations-to-style
{:tint-color icon-color-anim}
{:width 24
:height 24
:margin-left 33
:margin-top 8})
background-color (reanimated/use-shared-value "transparent")
background-animated-style (reanimated/apply-animations-to-style
{:background-color background-color}
{:width 90
:height 40
:border-radius 10})]
[rn/touchable-without-feedback
{:test-ID test-ID
:on-press on-press
:on-press-in #(toggle-background-color background-color false pass-through?)
:on-press-out #(toggle-background-color background-color true pass-through?)
:accessibility-label accessibility-label}
[reanimated/view {:style background-animated-style}
;; In android animations are not working for the animated components which are nested by
;; hole-view,
;; Interestingly this only happens when hole view and blur view are used together
;; Similar behavior is also seen while removing holes, and to fix that we used key and
;; force-rendered view
;; But we need animations faster for tab clicks, so we can't rely on reagent atoms,
;; so for now only using hole view for the ios tab icon notification boundary
(if platform/ios?
[hole-view/hole-view
{:key new-notifications? ;; Key is required to force removal of holes
:holes (cond
(not new-notifications?) ;; No new notifications, remove holes
[]
(= notification-indicator :unread-dot)
[{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}]
(= notification-indicator :unread-dot)
[{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}]
:else
[{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])}
[reanimated/image
{:style icon-animated-style
:source (icons/icon-source (keyword (str icon 24)))}]]
[reanimated/image
{:style icon-animated-style
:source (icons/icon-source (keyword (str icon 24)))}])
(when new-notifications?
(if (= notification-indicator :counter)
[counter/counter
{:override-text-color colors/white
:override-bg-color colors/primary-50
:style {:position :absolute
:left 48
:top 2}}
counter-label]
[rn/view
{:style {:width 8
:height 8
:border-radius 4
:top 6
:left 51
:position :absolute
:background-color colors/primary-50}}]))]]))
(defn bottom-nav-tab
[opts]
[:f> f-bottom-nav-tab opts])
:else
[{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])}
[reanimated/image
{:style icon-animated-style
:source (icons/icon-source (keyword (str icon 24)))}]]
[reanimated/image
{:style icon-animated-style
:source (icons/icon-source (keyword (str icon 24)))}])
(when new-notifications?
(if (= notification-indicator :counter)
[counter/counter
{:override-text-color colors/white
:override-bg-color colors/primary-50
:style {:position :absolute
:left 48
:top 2}}
counter-label]
[rn/view
{:style {:width 8
:height 8
:border-radius 4
:top 6
:left 51
:position :absolute
:background-color colors/primary-50}}]))]]))])
@@ -14,42 +14,40 @@
:style style
:customization-color customization-color}]))
(defn- f-floating-shell-button
[dynamic-buttons style opacity-anim]
(let [original-style (merge {:flex-direction :row
:margin-horizontal 12
:pointer-events :box-none}
style)
animated-style (reanimated/apply-animations-to-style
(if opacity-anim
{:opacity opacity-anim}
{})
original-style)]
[reanimated/view {:style animated-style}
;; Left Section
[rn/view {:style {:flex 1}}
[dynamic-button-view :search dynamic-buttons
{:position :absolute
:right 8}]]
;; Mid Section (jump-to)
[dynamic-button-view :jump-to dynamic-buttons nil]
;; Right Section
[rn/view {:style {:flex 1}}
[rn/view
{:style {:position :absolute
:flex-direction :row
:right 0}}
[dynamic-button-view :mention dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-down dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-up dynamic-buttons {:margin-left 8}]
[dynamic-button-view :scroll-to-bottom dynamic-buttons {:margin-left 8}]]]]))
(defn floating-shell-button
"[floating-shell-button dynamic-buttons style opacity-anim pointer-anim]
dynamic-buttons {:button-type {:on-press on-press :count count}}
style override style
opacity-anim reanimated value (optional)"
([dynamic-buttons style]
[:f> f-floating-shell-button dynamic-buttons style nil])
([dynamic-button style]
(floating-shell-button dynamic-button style nil))
([dynamic-buttons style opacity-anim]
[:f> f-floating-shell-button dynamic-buttons style opacity-anim]))
[:f>
(fn []
(let [original-style (merge {:flex-direction :row
:margin-horizontal 12
:pointer-events :box-none}
style)
animated-style (reanimated/apply-animations-to-style
(if opacity-anim
{:opacity opacity-anim}
{})
original-style)]
[reanimated/view {:style animated-style}
;; Left Section
[rn/view {:style {:flex 1}}
[dynamic-button-view :search dynamic-buttons
{:position :absolute
:right 8}]]
;; Mid Section (jump-to)
[dynamic-button-view :jump-to dynamic-buttons nil]
;; Right Section
[rn/view {:style {:flex 1}}
[rn/view
{:style {:position :absolute
:flex-direction :row
:right 0}}
[dynamic-button-view :mention dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-down dynamic-buttons {:margin-left 8}]
[dynamic-button-view :notification-up dynamic-buttons {:margin-left 8}]
[dynamic-button-view :scroll-to-bottom dynamic-buttons {:margin-left 8}]]]]))]))
+4 -7
View File
@@ -30,8 +30,7 @@
{:no-color true})))
(defn left-section-view
[{:keys [on-press icon accessibility-label type icon-background-color icon-override-theme]
:or {type :grey}}
[{:keys [on-press icon accessibility-label type icon-background-color] :or {type :grey}}
put-middle-section-on-left?]
[rn/view {:style (when put-middle-section-on-left? {:margin-right 5})}
[button/button
@@ -40,7 +39,6 @@
:type type
:size 32
:accessibility-label accessibility-label
:override-theme icon-override-theme
:override-background-color icon-background-color}
icon]])
@@ -152,7 +150,7 @@
:justify-content :flex-end)}
(let [last-icon-index (-> right-section-buttons count dec)]
(map-indexed (fn [index
{:keys [icon on-press type style icon-override-theme accessibility-label label]
{:keys [icon on-press type style icon-override-theme accessibility-label]
:or {type :grey}}]
^{:key index}
[rn/view
@@ -163,12 +161,11 @@
:accessible true))
[button/button
{:on-press on-press
:icon (not label)
:icon true
:type type
:before (when label icon)
:size 32
:override-theme icon-override-theme}
(if label label icon)]])
icon]])
right-section-buttons))])
(defn page-nav
@@ -1,38 +0,0 @@
(ns quo2.components.notifications.notification.component-spec
(:require
[quo2.components.markdown.text :as text]
[quo2.components.notifications.notification.view :as notification]
[test-helpers.component :as h]))
(h/describe "notification"
(h/test "empty notification"
(h/render [notification/notification {}])
(h/is-null (h/query-by-label-text :notification-avatar))
(h/is-null (h/query-by-label-text :notification-header))
(h/is-null (h/query-by-label-text :notification-body)))
(h/test "notification with title and text"
(h/render [notification/notification
{:title "title"
:title-weight :medium
:text "text"}])
(-> (h/expect (h/get-by-label-text :notification-header))
(.toHaveTextContent "title"))
(-> (h/expect (h/get-by-label-text :notification-body))
(.toHaveTextContent "text")))
(h/test "notification with custom input"
(h/render [notification/notification
{:header [text/text {:accessibility-label :header} "custom header"]
:avatar [text/text {:accessibility-label :avatar} "custom avatar"]
:body [text/text {:accessibility-label :body} "custom body"]}])
(h/is-truthy (h/get-by-label-text :notification-avatar))
(h/is-truthy (h/get-by-label-text :notification-header))
(h/is-truthy (h/get-by-label-text :notification-body))
(h/is-truthy (h/get-by-label-text :avatar))
(h/is-truthy (h/get-by-label-text :header))
(h/is-truthy (h/get-by-label-text :body))
(-> (h/expect (h/get-by-label-text :notification-header))
(.toHaveTextContent "custom header"))
(-> (h/expect (h/get-by-label-text :notification-avatar))
(.toHaveTextContent "custom avatar"))
(-> (h/expect (h/get-by-label-text :notification-body))
(.toHaveTextContent "custom body"))))
@@ -1,39 +0,0 @@
(ns quo2.components.notifications.notification.style
(:require
[quo2.foundations.colors :as colors]
[quo2.foundations.shadows :as shadows]))
(def box-container
{:margin-horizontal 8
:border-radius 16
:overflow :hidden})
(def blur-container
{:height "100%"
:width "100%"
:position :absolute
:padding-vertical 8
:padding-left 10
:padding-right 8
:background-color :transparent})
(defn content-container
[override-theme]
(merge
(:shadow-1 shadows/normal-scale)
{:background-color (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 override-theme)
:flex-direction :row
:padding-vertical 8
:padding-horizontal 12}))
(def right-side-container {:flex 1})
(defn title
[override-theme]
{:color (colors/theme-colors colors/white colors/neutral-100 override-theme)})
(defn text
[override-theme]
{:color (colors/theme-colors colors/white colors/neutral-100 override-theme)})
(def avatar-container {:margin-right 8 :margin-top 4})
@@ -1,74 +0,0 @@
(ns quo2.components.notifications.notification.view
(:require [quo2.components.markdown.text :as text]
[quo2.components.notifications.notification.style :as style]
[react-native.blur :as blur]
[react-native.core :as rn]))
(defn header-container
[& children]
[into
[rn/view {:accessibility-label :notification-header}] children])
(defn body-container
[& children]
[into [rn/view {:accessibility-label :notification-body}] children])
(defn avatar-container
[& children]
[into
[rn/view
{:style style/avatar-container
:accessibility-label :notification-avatar}]
children])
(defn title
([text weight] (title text weight nil))
([text weight override-theme]
[text/text
{:size :paragraph-1
:weight (or weight :semi-bold)
:style (style/title override-theme)
:accessibility-label :notification-title}
text]))
(defn message
[text override-theme]
[text/text
{:size :paragraph-2
:style (style/text override-theme)
:accessibility-label :notification-content}
text])
(defn- notification-container
[{:keys [avatar header body container-style override-theme]}]
[rn/view
{:style (merge style/box-container container-style)}
[blur/view
{:style style/blur-container
:blur-amount 13
:blur-radius 10
:blur-type :transparent
:overlay-color :transparent}]
[rn/view
{:style (style/content-container override-theme)}
avatar
[rn/view
{:style style/right-side-container}
header
body]]])
(defn notification
[{title-text :title :keys [avatar header title-weight text body container-style override-theme]}]
(let [header (or header
(when title-text
[title title-text title-weight override-theme]))
header (when header [header-container header])
body (or body (when text [message text override-theme]))
body (when body [body-container body])
avatar (when avatar [avatar-container avatar])]
[notification-container
{:avatar avatar
:header header
:body body
:container-style container-style
:override-theme override-theme}]))
@@ -0,0 +1,122 @@
(ns quo2.components.notifications.toast
(:require [quo2.components.icon :as icon]
[quo2.components.markdown.text :as text]
[quo2.components.notifications.count-down-circle :as count-down-circle]
[quo2.foundations.colors :as colors]
[quo2.foundations.shadows :as shadows]
[quo2.theme :as theme]
[utils.i18n :as i18n]
[react-native.blur :as blur]
[react-native.core :as rn]))
(def ^:private themes
{:container {:dark {:background-color colors/white-opa-70}
:light {:background-color colors/neutral-80-opa-70}}
:title {:dark {:color colors/neutral-100}
:light {:color colors/white}}
:text {:dark {:color colors/neutral-100}
:light {:color colors/white}}
:icon {:dark {:color colors/neutral-100}
:light {:color colors/white}}
:action-container {:dark {:background-color colors/neutral-80-opa-5}
:light {:background-color colors/white-opa-5}}})
(defn- merge-theme-style
[component-key styles override-theme]
(merge (get-in themes [component-key (or override-theme (theme/get-theme))]) styles))
(defn toast-action-container
[{:keys [on-press style]} & children]
[rn/touchable-highlight
{:on-press on-press
:underlay-color :transparent}
[into
[rn/view
{:style (merge
{:flex-direction :row
:padding-vertical 3
:padding-horizontal 8
:align-items :center
:border-radius 8
:background-color (get-in themes
[:action-container (theme/get-theme)
:background-color])}
style)}]
children]])
(defn toast-undo-action
[duration on-press override-theme]
[toast-action-container
{:on-press on-press :accessibility-label :toast-undo-action}
[rn/view {:style {:margin-right 5}}
[count-down-circle/circle-timer {:duration duration}]]
[text/text
{:size :paragraph-2 :weight :medium :style (merge-theme-style :text {} override-theme)}
[i18n/label :t/undo]]])
(defn- toast-container
[{:keys [left title text right container-style override-theme]}]
[rn/view
{:style (merge {:margin-horizontal 12
:border-radius 12
:overflow :hidden}
container-style)}
[blur/view
{:style {:height "100%"
:width "100%"
:position :absolute
:padding-vertical 8
:padding-left 10
:padding-right 8
:background-color :transparent}
:blur-amount 13
:blur-radius 10
:blur-type :transparent
:overlay-color :transparent}]
[rn/view
{:style (merge-theme-style :container
(merge
(:shadow-1 shadows/normal-scale)
{:flex-direction :row
:justify-content :space-between
:padding-vertical 8
:padding-left 10
:padding-right 8
:border-radius 12})
override-theme)}
[rn/view {:style {:padding 2}} left]
[rn/view {:style {:padding 4 :flex 1}}
(when title
[text/text
{:size :paragraph-1
:weight :semi-bold
:style (merge-theme-style :title {} override-theme)
:accessibility-label :toast-title}
title])
(when text
[text/text
{:size :paragraph-2
:weight :medium
:style (merge-theme-style :text {} override-theme)
:accessibility-label :toast-content}
text])]
(when right right)]])
(defn toast
[{:keys [icon icon-color title text action undo-duration undo-on-press container-style
override-theme]}]
[toast-container
{:left (when icon
[icon/icon icon
{:container-style {:width 20 :height 20}
:color (or icon-color
(get-in themes
[:icon (or override-theme (theme/get-theme))
:color]))}])
:title title
:text text
:right (if undo-duration
[toast-undo-action undo-duration undo-on-press override-theme]
action)
:container-style container-style
:override-theme override-theme}])
@@ -1,55 +0,0 @@
(ns quo2.components.notifications.toast.style
(:require
[quo2.foundations.colors :as colors]
[quo2.foundations.shadows :as shadows]))
(def box-container
{:margin-horizontal 12
:border-radius 12
:overflow :hidden})
(def blur-container
{:height "100%"
:width "100%"
:position :absolute
:padding-vertical 8
:padding-left 10
:padding-right 8
:background-color :transparent})
(defn content-container
[override-theme]
(merge
(:shadow-1 shadows/normal-scale)
{:background-color (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 override-theme)
:flex-direction :row
:justify-content :space-between
:padding-vertical 8
:padding-left 10
:padding-right 8
:border-radius 12}))
(defn title
[override-theme]
{:color (colors/theme-colors colors/white colors/neutral-100 override-theme)})
(defn text
[override-theme]
{:color (colors/theme-colors colors/white colors/neutral-100 override-theme)})
(defn icon
[override-theme]
{:color (colors/theme-colors colors/white colors/neutral-100 override-theme)
:container-style {:width 20 :height 20}})
(def left-side-container {:padding 2})
(def right-side-container {:padding 4 :flex 1})
(defn action-container
[override-theme]
{:background-color (colors/theme-colors colors/white-opa-5 colors/neutral-80-opa-5 override-theme)
:flex-direction :row
:padding-vertical 3
:padding-horizontal 8
:align-items :center
:border-radius 8})
@@ -1,80 +0,0 @@
(ns quo2.components.notifications.toast.view
(:require [quo2.components.avatars.user-avatar.view :as user-avatar]
[quo2.components.icon :as icon]
[quo2.components.markdown.text :as text]
[quo2.components.notifications.count-down-circle :as count-down-circle]
[quo2.components.notifications.toast.style :as style]
[quo2.theme :as theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[utils.i18n :as i18n]))
(defn toast-action-container
[{:keys [on-press style]} & children]
[rn/touchable-highlight
{:on-press on-press
:underlay-color :transparent}
[into
[rn/view
{:style (merge (style/action-container (theme/get-theme)) style)}]
children]])
(defn toast-undo-action
[duration on-press override-theme]
[toast-action-container
{:on-press on-press :accessibility-label :toast-undo-action}
[rn/view {:style {:margin-right 5}}
[count-down-circle/circle-timer {:duration duration}]]
[text/text
{:size :paragraph-2 :weight :medium :style (style/text override-theme)}
[i18n/label :t/undo]]])
(defn- toast-container
[{:keys [left title text right container-style override-theme]}]
[rn/view
{:style (merge style/box-container container-style)}
[blur/view
{:style style/blur-container
:blur-amount 13
:blur-radius 10
:blur-type :transparent
:overlay-color :transparent}]
[rn/view
{:style (style/content-container override-theme)}
[rn/view {:style style/left-side-container} left]
[rn/view {:style style/right-side-container}
(when title
[text/text
{:size :paragraph-1
:weight :semi-bold
:style (style/title override-theme)
:accessibility-label :toast-title}
title])
(when text
[text/text
{:size :paragraph-2
:weight :medium
:style (style/text override-theme)
:accessibility-label :toast-content}
text])]
(when right right)]])
(defn toast
[{:keys [icon icon-color title text action undo-duration undo-on-press container-style
override-theme user]}]
[toast-container
{:left (cond icon
[icon/icon icon
(cond-> (style/icon override-theme)
icon-color
(assoc :color icon-color))]
user
[user-avatar/user-avatar user])
:title title
:text text
:right (if undo-duration
[toast-undo-action undo-duration undo-on-press override-theme]
action)
:container-style container-style
:override-theme override-theme}])
@@ -10,7 +10,7 @@
[quo2.components.profile.profile-card.style :as style]
[quo2.components.avatars.user-avatar.view :as user-avatar]))
(defn- f-profile-card-component
(defn- profile-card-component
[{:keys [keycard-account? profile-picture name hash
customization-color emoji-hash on-options-press
show-emoji-hash? show-options-button? show-user-hash?
@@ -25,7 +25,7 @@
last-item? false
card-style {:padding-horizontal 20
:flex 1}}}]
(let [{:keys [width]} (rn/get-window)
(let [{:keys [width]} (rn/use-window-dimensions)
padding-bottom (cond
login-card? 38
show-emoji-hash? 12
@@ -104,4 +104,4 @@
(defn profile-card
[props]
[:f> f-profile-card-component props])
[:f> profile-card-component props])
@@ -6,83 +6,85 @@
[react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :as helpers]))
(defn f-delete-button
(defn delete-button
[recording? ready-to-delete? reviewing-audio? force-show-controls?]
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-x (reanimated/use-shared-value (if force-show-controls? 35 20))
scale (reanimated/use-shared-value (if force-show-controls? 0.75 1))
connector-opacity (reanimated/use-shared-value 0)
connector-width (reanimated/use-shared-value 24)
connector-height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-animation (fn []
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-x-animation (fn []
(helpers/animate-linear translate-x 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x 0 200)
(helpers/animate-linear opacity 1 200))
fade-out-animation (fn []
(helpers/animate-linear
translate-x
(if @reviewing-audio? 35 20)
200)
(if @reviewing-audio?
(helpers/animate-linear scale 0.75 200)
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-x 20 0 200)
(helpers/animate-linear-with-delay scale 1 0 200))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-delete?
(start-x-animation)
@recording?
(reset-x-animation)))
[@ready-to-delete?])
[:<>
[reanimated/view {:style (style/delete-button-container opacity)}
[reanimated/view
{:style (style/delete-button-connector connector-opacity
connector-width
connector-height
border-radius-first-half
border-radius-second-half)}]]
[reanimated/view
{:style (style/delete-button scale translate-x opacity)
:pointer-events :none}
[icons/icon :i/delete
{:color colors/white
:size 20}]]]))
[:f>
(fn []
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-x (reanimated/use-shared-value (if force-show-controls? 35 20))
scale (reanimated/use-shared-value (if force-show-controls? 0.75 1))
connector-opacity (reanimated/use-shared-value 0)
connector-width (reanimated/use-shared-value 24)
connector-height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-animation (fn []
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-x-animation (fn []
(helpers/animate-linear translate-x 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x 0 200)
(helpers/animate-linear opacity 1 200))
fade-out-animation (fn []
(helpers/animate-linear
translate-x
(if @reviewing-audio? 35 20)
200)
(if @reviewing-audio?
(helpers/animate-linear scale 0.75 200)
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-x 20 0 200)
(helpers/animate-linear-with-delay scale 1 0 200))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-delete?
(start-x-animation)
@recording?
(reset-x-animation)))
[@ready-to-delete?])
[:<>
[reanimated/view {:style (style/delete-button-container opacity)}
[reanimated/view
{:style (style/delete-button-connector connector-opacity
connector-width
connector-height
border-radius-first-half
border-radius-second-half)}]]
[reanimated/view
{:style (style/delete-button scale translate-x opacity)
:pointer-events :none}
[icons/icon :i/delete
{:color colors/white
:size 20}]]]))])
@@ -6,74 +6,76 @@
[react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :as helpers]))
(defn f-lock-button
(defn lock-button
[recording? ready-to-lock? locked?]
(let [translate-x-y (reanimated/use-shared-value 20)
opacity (reanimated/use-shared-value 0)
connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 24)
height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-y-animation (fn []
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
(helpers/animate-easing-with-delay width 56 83.33 63.33)
(helpers/animate-easing-with-delay height 56 83.33 63.33)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
63.33))
reset-x-y-animation (fn []
(helpers/animate-linear translate-x-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x-y 0 220)
(helpers/animate-linear opacity 1 220))
fade-out-animation (fn []
(helpers/animate-linear translate-x-y 20 200)
(helpers/animate-linear opacity 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(cond
@ready-to-lock?
(start-x-y-animation)
(and @recording? (not @locked?))
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @locked?
(fade-out-animation)
(reset-x-y-animation)))
[@locked?])
[:<>
[reanimated/view {:style (style/lock-button-container opacity)}
[reanimated/view
{:style (style/lock-button-connector connector-opacity
width
height
border-radius-first-half
border-radius-second-half)}]]
[reanimated/view
{:style (style/lock-button translate-x-y opacity)
:pointer-events :none}
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
{:color (colors/theme-colors colors/black colors/white)
:size 20}]]]))
[:f>
(fn []
(let [translate-x-y (reanimated/use-shared-value 20)
opacity (reanimated/use-shared-value 0)
connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 24)
height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-y-animation (fn []
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
(helpers/animate-easing-with-delay width 56 83.33 63.33)
(helpers/animate-easing-with-delay height 56 83.33 63.33)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
63.33))
reset-x-y-animation (fn []
(helpers/animate-linear translate-x-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x-y 0 220)
(helpers/animate-linear opacity 1 220))
fade-out-animation (fn []
(helpers/animate-linear translate-x-y 20 200)
(helpers/animate-linear opacity 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(cond
@ready-to-lock?
(start-x-y-animation)
(and @recording? (not @locked?))
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @locked?
(fade-out-animation)
(reset-x-y-animation)))
[@locked?])
[:<>
[reanimated/view {:style (style/lock-button-container opacity)}
[reanimated/view
{:style (style/lock-button-connector connector-opacity
width
height
border-radius-first-half
border-radius-second-half)}]]
[reanimated/view
{:style (style/lock-button translate-x-y opacity)
:pointer-events :none}
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
{:color (colors/theme-colors colors/black colors/white)
:size 20}]]]))])
@@ -7,20 +7,22 @@
[quo2.components.buttons.button :as button]
[quo2.components.record-audio.record-audio.helpers :as helpers]))
(defn f-record-button
(defn record-button
[recording? reviewing-audio?]
(let [opacity (reanimated/use-shared-value 1)
show-animation #(helpers/set-value opacity 1)
hide-animation #(helpers/set-value opacity 0)]
(use-effect (fn []
(if (or @recording? @reviewing-audio?)
(hide-animation)
(show-animation)))
[@recording? @reviewing-audio?])
[reanimated/view {:style (style/record-button-container opacity)}
[button/button
{:type :outline
:size 32
:width 32
:accessibility-label :mic-button}
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]]))
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 1)
show-animation #(helpers/set-value opacity 1)
hide-animation #(helpers/set-value opacity 0)]
(use-effect (fn []
(if (or @recording? @reviewing-audio?)
(hide-animation)
(show-animation)))
[@recording? @reviewing-audio?])
[reanimated/view {:style (style/record-button-container opacity)}
[button/button
{:type :outline
:size 32
:width 32
:accessibility-label :mic-button}
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]]))])
@@ -27,175 +27,177 @@
(reagent/as-element
[reanimated/view {:style (style/animated-circle scale opacity color)}]))))))
(defn f-record-button-big
(defn record-button-big
[recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating?
record-button-at-initial-position? locked? reviewing-audio? recording-timer recording-length-ms
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (worklets.record-audio/ring-scale scale
(* scale-padding
index))]
{:scale ring-scale
:opacity (reanimated/interpolate ring-scale
[1 scale-to-each]
[opacity-from 0])}))
(range 0 5))
rings-color (cond
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
translate-y (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 0)
button-color colors/primary-50
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white)
icon-opacity (reanimated/use-shared-value 1)
red-overlay-opacity (reanimated/use-shared-value 0)
gray-overlay-opacity (reanimated/use-shared-value 0)
complete-animation
(fn []
(cond
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @locked?) (not @reviewing-audio?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration @recording-length-ms}))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder-fn)
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
start-animation (fn []
(helpers/set-value opacity 1)
(helpers/animate-linear scale 2.6 signal-anim-duration)
;; TODO: Research if we can implement this with withSequence method
;; from Reanimated 2
;; GitHub issue [#14561]:
;; https://github.com/status-im/status-mobile/issues/14561
(reset! clear-timeout
(js/setTimeout
(fn []
(helpers/set-value scale scale-to-each)
(helpers/animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration)))
stop-animation (fn []
(helpers/set-value opacity 0)
(reanimated/cancel-animation scale)
(helpers/set-value scale 1)
(when @clear-timeout (js/clearTimeout @clear-timeout)))
start-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-linear icon-opacity 1 500)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-x -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(helpers/animate-linear red-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-x-animation (fn []
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear red-overlay-opacity 0 100)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -44 200)
(helpers/animate-easing translate-x -44 200)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
(helpers/animate-linear gray-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
reset-x-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800))]
(use-effect (fn []
[:f>
(fn []
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (worklets.record-audio/ring-scale scale
(* scale-padding
index))]
{:scale ring-scale
:opacity (reanimated/interpolate ring-scale
[1 scale-to-each]
[opacity-from 0])}))
(range 0 5))
rings-color (cond
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
translate-y (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 0)
button-color colors/primary-50
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white)
icon-opacity (reanimated/use-shared-value 1)
red-overlay-opacity (reanimated/use-shared-value 0)
gray-overlay-opacity (reanimated/use-shared-value 0)
complete-animation
(fn []
(cond
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @locked?) (not @reviewing-audio?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
@recording?
(start-animation)
(not @ready-to-lock?)
(stop-animation)))
[@recording?])
(use-effect (fn []
(if @ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @ready-to-send?
(start-y-animation)
(reset-y-animation)))
[@ready-to-send?])
(use-effect (fn []
(if @ready-to-delete?
(start-x-animation)
(reset-x-animation)))
[@ready-to-delete?])
[reanimated/view
{:style (style/record-button-big-container translate-x translate-y opacity)
:pointer-events :none}
[:<>
(map-indexed
(fn [id animation]
^{:key id}
[animated-ring
{:scale (:scale animation)
:opacity (:opacity animation)
:color rings-color}])
animations)]
[rn/view {:style (style/record-button-big-body button-color)}
[reanimated/view {:style (style/record-button-big-red-overlay red-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-gray-overlay gray-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-icon-container icon-opacity)}
(if @locked?
[rn/view {:style style/stop-icon}]
[icons/icon :i/audio {:color icon-color}])]]]))
@ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration @recording-length-ms}))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder-fn)
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
start-animation (fn []
(helpers/set-value opacity 1)
(helpers/animate-linear scale 2.6 signal-anim-duration)
;; TODO: Research if we can implement this with withSequence method
;; from Reanimated 2
;; GitHub issue [#14561]:
;; https://github.com/status-im/status-mobile/issues/14561
(reset! clear-timeout
(js/setTimeout
(fn []
(helpers/set-value scale scale-to-each)
(helpers/animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration)))
stop-animation (fn []
(helpers/set-value opacity 0)
(reanimated/cancel-animation scale)
(helpers/set-value scale 1)
(when @clear-timeout (js/clearTimeout @clear-timeout)))
start-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-linear icon-opacity 1 500)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-x -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(helpers/animate-linear red-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-x-animation (fn []
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear red-overlay-opacity 0 100)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -44 200)
(helpers/animate-easing translate-x -44 200)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
(helpers/animate-linear gray-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
reset-x-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800))]
(use-effect (fn []
(cond
@recording?
(start-animation)
(not @ready-to-lock?)
(stop-animation)))
[@recording?])
(use-effect (fn []
(if @ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @ready-to-send?
(start-y-animation)
(reset-y-animation)))
[@ready-to-send?])
(use-effect (fn []
(if @ready-to-delete?
(start-x-animation)
(reset-x-animation)))
[@ready-to-delete?])
[reanimated/view
{:style (style/record-button-big-container translate-x translate-y opacity)
:pointer-events :none}
[:<>
(map-indexed
(fn [id animation]
^{:key id}
[animated-ring
{:scale (:scale animation)
:opacity (:opacity animation)
:color rings-color}])
animations)]
[rn/view {:style (style/record-button-big-body button-color)}
[reanimated/view {:style (style/record-button-big-red-overlay red-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-gray-overlay gray-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-icon-container icon-opacity)}
(if @locked?
[rn/view {:style style/stop-icon}]
[icons/icon :i/audio {:color icon-color}])]]]))])

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