Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
702b8b5d9f | ||
|
|
2fd9e80650 |
@@ -100,8 +100,9 @@ Statusgo.xcframework
|
||||
|
||||
#ios
|
||||
/ios/Pods/
|
||||
/ios/logs/
|
||||
/ios/StatusIm.xcworkspace
|
||||
/ios/react-native-xcode.log
|
||||
/ios/set_xcode_version.log
|
||||
.ruby-version
|
||||
status-e2e/
|
||||
|
||||
@@ -182,4 +183,4 @@ test/appium/tests/users.py
|
||||
/.calva/
|
||||
|
||||
## component-tests
|
||||
*.log
|
||||
*.log
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
@@ -63,9 +63,6 @@ pipeline {
|
||||
steps {
|
||||
script { api = ios.bundle() }
|
||||
}
|
||||
post {
|
||||
failure { archiveArtifacts 'ios/logs/*' }
|
||||
}
|
||||
}
|
||||
stage('Parallel Upload') {
|
||||
parallel {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
agent { label params.AGENT_LABEL }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
agent { label 'macos' }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.9'
|
||||
library 'status-jenkins-lib@v1.6.6'
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
|
||||
@@ -116,20 +116,6 @@ deref'ed atoms.
|
||||
{:background-color (colors/theme-colors colors/white colors/neutral-90)})
|
||||
```
|
||||
|
||||
### Custom Colors
|
||||
|
||||
The Status designs have a lot of customization of user and group colors with components and pages. For consistency it is best to use `customization-color` as the prop key on pages and components. This will help easily identify what pages and components in the application are using customized colors.
|
||||
|
||||
```clojure
|
||||
;; bad
|
||||
(defn community-card [{keys [custom-color]}]
|
||||
...)
|
||||
|
||||
;; good
|
||||
(defn community-card [{keys [customization-color]}]
|
||||
...)
|
||||
```
|
||||
|
||||
### Using TODOs comments
|
||||
|
||||
_TODO_ comments are used extensively in the codebase, but prefer to use them
|
||||
|
||||
@@ -123,7 +123,6 @@ def build_ios_adhoc(readonly: false, pr_build: false)
|
||||
export_method: 'ad-hoc',
|
||||
output_name: 'StatusIm',
|
||||
output_directory: 'status-ios',
|
||||
buildlog_path: 'ios/logs',
|
||||
export_options: {
|
||||
signingStyle: 'manual',
|
||||
provisioningProfiles: {
|
||||
@@ -163,7 +162,6 @@ def build_ios_e2e
|
||||
# 3. directory where to up StatusIm.app
|
||||
derived_data_path: 'status-ios',
|
||||
output_name: 'StatusIm.app',
|
||||
buildlog_path: 'ios/logs',
|
||||
# -------------------------------------
|
||||
# Normal stuff
|
||||
scheme: 'StatusIm',
|
||||
@@ -241,7 +239,6 @@ platform :ios do
|
||||
clean: true,
|
||||
export_method: 'app-store',
|
||||
output_directory: 'status-ios',
|
||||
buildlog_path: 'ios/logs',
|
||||
include_symbols: false,
|
||||
export_options: {
|
||||
"combileBitcode": true,
|
||||
|
||||
@@ -526,7 +526,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/usr/bin/env sh";
|
||||
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";
|
||||
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 > ./react-native-xcode.log 2>&1";
|
||||
};
|
||||
0F876BD5356F61BF142A01A0 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@@ -584,7 +584,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/usr/bin/env sh";
|
||||
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";
|
||||
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 > ./react-native-xcode.log 2>&1";
|
||||
};
|
||||
3AAD2AD724A3A60E0075D594 /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@@ -598,7 +598,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
shellPath = "/usr/bin/env sh";
|
||||
shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./logs/set_xcode_version.log 2>&1";
|
||||
shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./set_xcode_version.log 2>&1";
|
||||
};
|
||||
3C1038075AE5E6FB86AC2319 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@@ -678,7 +678,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
shellPath = "/usr/bin/env sh";
|
||||
shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./logs/set_xcode_version.log 2>&1";
|
||||
shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./set_xcode_version.log 2>&1";
|
||||
};
|
||||
E732E3E1B024946173BF6D3D /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# Description
|
||||
|
||||
This directory is the destination of logs created by Fastlane and React Native Xcode script.
|
||||
|
||||
# Logs
|
||||
|
||||
* `react-native-xcode.log` - Output from `node_modules/react-native/scripts/react-native-xcode.sh`.
|
||||
- Created by redirecting output of `shellScript` in `ios/StatusIm.xcodeproj/project.pbxproj`.
|
||||
* `Status PR-StatusImPR.log - Created by [Fastlane Gym](https://docs.fastlane.tools/actions/gym/).
|
||||
- Configred via [`Fastfile`](../Fastfile) using `buildlog_path` argument for `build_ios_app`.
|
||||
|
||||
# CI
|
||||
|
||||
These log files are uploaded to Jenkins as CI job artifacts on failure using `archiveArtifacts`.
|
||||
@@ -12,7 +12,7 @@ in mkShell {
|
||||
|
||||
buildInputs = with pkgs; lib.unique ([
|
||||
# core utilities that should always be present in a shell
|
||||
bash curl wget file unzip flock procps
|
||||
bash curl wget file unzip flock
|
||||
git gnumake jq ncurses gnugrep parallel
|
||||
lsof # used in start-react-native.sh
|
||||
# build specific utilities
|
||||
@@ -22,7 +22,7 @@ in mkShell {
|
||||
# other nice to have stuff
|
||||
yarn nodejs python27
|
||||
] # and some special cases
|
||||
++ lib.optionals stdenv.isDarwin [ cocoapods clang tcl ]
|
||||
++ lib.optionals stdenv.isDarwin [ cocoapods clang ]
|
||||
++ lib.optionals (!stdenv.isDarwin) [ gcc8 ]
|
||||
);
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 864 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 946 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 927 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 768 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 539 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
@@ -1,10 +0,0 @@
|
||||
import { useDerivedValue } from 'react-native-reanimated';
|
||||
|
||||
export function infoLayout(input, isTop) {
|
||||
return useDerivedValue(
|
||||
function () {
|
||||
'worklet'
|
||||
return isTop ? input.value : -input.value
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import {useAnimatedScrollHandler} from "react-native-reanimated";
|
||||
|
||||
export function useAnimatedScrollHandlerWorklet(scrollY) {
|
||||
return useAnimatedScrollHandler((event) => {
|
||||
scrollY.value = event.contentOffset.y;
|
||||
})
|
||||
}
|
||||
@@ -147,10 +147,7 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
|
||||
:SvgXml #js {:render identity}
|
||||
:default #js {:render identity}})
|
||||
(def react-native-webview #js {:default {}})
|
||||
(def react-native-audio-toolkit
|
||||
#js
|
||||
{:MediaStates {}
|
||||
:PlaybackCategories {}})
|
||||
(def react-native-audio-toolkit #js {:MediaStates {}})
|
||||
(def net-info #js {})
|
||||
(def touchid #js {})
|
||||
(def react-native-image-viewing #js {:default {}})
|
||||
@@ -355,6 +352,11 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
|
||||
(def worklet-factory
|
||||
#js {:applyAnimationsToStyle (fn [])})
|
||||
|
||||
(def shell-worklets #js {})
|
||||
|
||||
(def bottom-sheet #js {})
|
||||
(def record-audio-worklets #js {})
|
||||
|
||||
;; Update i18n_resources.cljs
|
||||
(defn mock
|
||||
[module]
|
||||
@@ -402,12 +404,10 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
|
||||
"@react-native-async-storage/async-storage" async-storage
|
||||
"react-native-svg" react-native-svg
|
||||
"react-native-orientation-locker" react-native-orientation-locker
|
||||
"../src/js/worklets/core.js" worklet-factory
|
||||
"../src/js/worklets/shell.js" #js {}
|
||||
"../src/js/worklets/bottom_sheet.js" #js {}
|
||||
"../src/js/worklets/record_audio.js" #js {}
|
||||
"../src/js/worklets/scroll_view.js" #js {}
|
||||
"../src/js/worklets/lightbox.js" #js {}
|
||||
"../src/js/worklet_factory.js" worklet-factory
|
||||
"../src/js/shell_worklets.js" shell-worklets
|
||||
"../src/js/bottom_sheet.js" bottom-sheet
|
||||
"../src/js/record_audio_worklets.js" record-audio-worklets
|
||||
"./fleets.js" default-fleets
|
||||
"@walletconnect/client" wallet-connect-client
|
||||
"../translations/ar.json" (js/JSON.parse (slurp "./translations/ar.json"))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.animated-header-flatlist.style
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
[react-native.reanimated :as ra]))
|
||||
|
||||
(defn container-view
|
||||
[view-height]
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
(defn blur-view
|
||||
[animation]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity animation}
|
||||
{:position :absolute
|
||||
:top 0
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
(defn entity-picture
|
||||
[animation]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:width animation
|
||||
:height animation}
|
||||
{:transform [{:scale 1}]
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
(defn header-bottom-part
|
||||
[animation]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:border-top-right-radius animation
|
||||
:border-top-left-radius animation}
|
||||
{:position :absolute
|
||||
@@ -66,9 +66,9 @@
|
||||
|
||||
(defn header-comp
|
||||
[y-animation opacity-animation]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
;; here using `left` won't work on Android, so we are using `translateX`
|
||||
{:transform [{:translateX (reanimated/use-shared-value 64)} {:translateY y-animation}]
|
||||
{:transform [{:translateX (ra/use-val 64)} {:translateY y-animation}]
|
||||
:opacity opacity-animation}
|
||||
{:position :absolute
|
||||
:z-index 3}))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[quo2.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[react-native.fast-image :as fast-image]
|
||||
[reagent.core :as reagent]
|
||||
@@ -18,16 +18,16 @@
|
||||
|
||||
(defn interpolate
|
||||
[value input-range output-range]
|
||||
(reanimated/interpolate value
|
||||
input-range
|
||||
output-range
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"}))
|
||||
(ra/interpolate value
|
||||
input-range
|
||||
output-range
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"}))
|
||||
|
||||
(defn scroll-handler
|
||||
[event initial-y scroll-y]
|
||||
(let [current-y (- (oops/oget event "nativeEvent.contentOffset.y") initial-y)]
|
||||
(reanimated/set-shared-value scroll-y current-y)))
|
||||
(ra/set-val scroll-y current-y)))
|
||||
|
||||
(defn header
|
||||
[{:keys [theme-color display-picture-comp cover-uri title-comp]} top-inset scroll-y]
|
||||
@@ -45,9 +45,9 @@
|
||||
{:style {:width "100%"
|
||||
:height cover-height}
|
||||
:source {:uri cover-uri}}])
|
||||
[reanimated/view {:style (style/header-bottom-part border-animation)}
|
||||
[ra/view {:style (style/header-bottom-part border-animation)}
|
||||
[title-comp]]
|
||||
[reanimated/view {:style (style/entity-picture size-animation)}
|
||||
[ra/view {:style (style/entity-picture size-animation)}
|
||||
[display-picture-comp image-animation]]]))
|
||||
|
||||
(defn animated-header-list
|
||||
@@ -64,7 +64,7 @@
|
||||
initial-y (if platform/ios? (- (:top insets)) 0)]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [scroll-y (reanimated/use-shared-value initial-y)
|
||||
(let [scroll-y (ra/use-val initial-y)
|
||||
opacity-animation (interpolate scroll-y
|
||||
[(* threshold 0.33) (* threshold 0.66)]
|
||||
[0 1])
|
||||
@@ -80,14 +80,14 @@
|
||||
{: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
|
||||
[ra/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)}
|
||||
[ra/view {:style (style/header-comp translate-animation title-opacity-animation)}
|
||||
[header-comp]]]
|
||||
[reanimated/flat-list
|
||||
[ra/flat-list
|
||||
{:data [nil]
|
||||
:render-fn main-comp
|
||||
:key-fn str
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
(ns quo2.components.avatars.user-avatar
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :refer [dark?]]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]))
|
||||
|
||||
(def sizes
|
||||
{:big {:outer 80
|
||||
:inner 72
|
||||
:status-indicator 20
|
||||
:status-indicator-border 4
|
||||
:font-size :heading-1}
|
||||
:medium {:outer 48
|
||||
:inner 44
|
||||
:status-indicator 12
|
||||
:status-indicator-border 2
|
||||
:font-size :paragraph-1}
|
||||
:small {:outer 32
|
||||
:inner 28
|
||||
:status-indicator 12
|
||||
:status-indicator-border 2
|
||||
:font-size :paragraph-2}
|
||||
:xs {:outer 24
|
||||
:inner 24
|
||||
:status-indicator 0
|
||||
:status-indicator-border 0
|
||||
:font-size :paragraph-2}
|
||||
:xxs {:outer 20
|
||||
:inner 20
|
||||
:status-indicator 0
|
||||
:status-indicator-border 0
|
||||
:font-size :label}
|
||||
:xxxs {:outer 16
|
||||
:inner 16
|
||||
:status-indicator 0
|
||||
:status-indicator-border 0
|
||||
:font-size :label}})
|
||||
|
||||
(defn dot-indicator
|
||||
[{:keys [size online? ring? dark?]}]
|
||||
(let [dimensions (get-in sizes [size :status-indicator])
|
||||
border-width (get-in sizes [size :status-indicator-border])
|
||||
right (case size
|
||||
:big 2
|
||||
:medium 0
|
||||
:small -2
|
||||
0)
|
||||
bottom (case size
|
||||
:big (if ring? -1 2)
|
||||
:medium (if ring? 0 -2)
|
||||
:small -2
|
||||
0)]
|
||||
[rn/view
|
||||
{:style {:background-color (if online?
|
||||
colors/success-50
|
||||
colors/neutral-40)
|
||||
:width dimensions
|
||||
:height dimensions
|
||||
:border-width border-width
|
||||
:border-radius dimensions
|
||||
:border-color (if dark?
|
||||
colors/neutral-100
|
||||
colors/white)
|
||||
:position :absolute
|
||||
:bottom bottom
|
||||
:right right}}]))
|
||||
|
||||
(defn initials-style
|
||||
[inner-dimensions outer-dimensions]
|
||||
{:position :absolute
|
||||
:top (/ (- outer-dimensions inner-dimensions) 2)
|
||||
:left (/ (- outer-dimensions inner-dimensions) 2)
|
||||
:width inner-dimensions
|
||||
:height inner-dimensions
|
||||
:border-radius inner-dimensions
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color (colors/custom-color-by-theme :turquoise 50 60)})
|
||||
|
||||
(defn outer-styles
|
||||
[outer-dimensions]
|
||||
{:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
:border-radius outer-dimensions})
|
||||
|
||||
(def one-initial-letter-sizes #{:xs :xxs :xxxs})
|
||||
(def valid-ring-sizes #{:big :medium :small})
|
||||
|
||||
(defn initials-avatar
|
||||
[{:keys [full-name size inner-dimensions outer-dimensions]}]
|
||||
(let [amount-initials (if (one-initial-letter-sizes size) 1 2)
|
||||
initials (as-> full-name $
|
||||
(string/split $ " ")
|
||||
(map (comp string/upper-case first) $)
|
||||
(take amount-initials $)
|
||||
(string/join $))
|
||||
font-size (get-in sizes [size :font-size])]
|
||||
[rn/view {:style (initials-style inner-dimensions outer-dimensions)}
|
||||
[text/text
|
||||
{:style {:color colors/white-opa-70}
|
||||
:weight :semi-bold
|
||||
:size font-size}
|
||||
initials]]))
|
||||
|
||||
(defn user-avatar
|
||||
"If no `profile-picture` is given, draws the initials based on the `full-name` and
|
||||
uses `ring-background` to display the ring behind the initials when given. Otherwise,
|
||||
shows the profile picture which already comes with the ring drawn over it."
|
||||
[{:keys [full-name status-indicator? online? size profile-picture ring-background]
|
||||
:or {status-indicator? true
|
||||
online? true
|
||||
size :big}}]
|
||||
(let [full-name (or full-name "empty name")
|
||||
draw-ring? (and ring-background (valid-ring-sizes size))
|
||||
outer-dimensions (get-in sizes [size :outer])
|
||||
inner-dimensions (get-in sizes [size (if draw-ring? :inner :outer)])]
|
||||
[rn/view
|
||||
{:style (outer-styles outer-dimensions)
|
||||
:accessibility-label :user-avatar}
|
||||
;; The `profile-picture` already has the ring in it
|
||||
(when-let [image (or profile-picture ring-background)]
|
||||
[fast-image/fast-image
|
||||
{:style (outer-styles outer-dimensions)
|
||||
:source image}])
|
||||
(when-not profile-picture
|
||||
[initials-avatar
|
||||
{:full-name full-name
|
||||
:size size
|
||||
:inner-dimensions inner-dimensions
|
||||
:outer-dimensions outer-dimensions}])
|
||||
(when status-indicator?
|
||||
[dot-indicator
|
||||
{:size size
|
||||
:online? online?
|
||||
:ring? draw-ring?
|
||||
:dark? (dark?)}])]))
|
||||
@@ -1,129 +0,0 @@
|
||||
(ns quo2.components.avatars.user-avatar.component-spec
|
||||
(:require [quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(defonce mock-picture (js/require "../resources/images/mock2/user_picture_male4.png"))
|
||||
|
||||
(h/describe "user avatar"
|
||||
(h/test "Default render"
|
||||
(h/render [user-avatar/user-avatar])
|
||||
(h/is-truthy (h/get-by-label-text :user-avatar))
|
||||
(h/is-truthy (h/get-by-text "EN")))
|
||||
|
||||
(h/describe "Profile picture"
|
||||
(h/test "Renders"
|
||||
(h/render
|
||||
[user-avatar/user-avatar {:profile-picture mock-picture}])
|
||||
(h/is-truthy (h/get-by-label-text :profile-picture)))
|
||||
|
||||
(h/test "Renders even if `:full-name` is passed"
|
||||
(h/render
|
||||
[user-avatar/user-avatar
|
||||
{:profile-picture mock-picture
|
||||
:full-name "New User1"}])
|
||||
(h/is-truthy (h/get-by-label-text :profile-picture))
|
||||
(h/is-null (h/query-by-label-text :initials-avatar)))
|
||||
|
||||
(h/describe "Status indicator"
|
||||
(h/test "Render"
|
||||
(h/render
|
||||
[user-avatar/user-avatar
|
||||
{:profile-picture mock-picture
|
||||
:status-indicator? true}])
|
||||
(h/is-truthy (h/get-by-label-text :profile-picture))
|
||||
(h/is-truthy (h/get-by-label-text :status-indicator)))
|
||||
|
||||
(h/test "Do not render"
|
||||
(h/render
|
||||
[user-avatar/user-avatar
|
||||
{:profile-picture mock-picture
|
||||
:status-indicator? false}])
|
||||
(h/is-truthy (h/get-by-label-text :profile-picture))
|
||||
(h/is-null (h/query-by-label-text :status-indicator)))))
|
||||
|
||||
(h/describe "Initials Avatar"
|
||||
(h/describe "Render initials"
|
||||
(letfn [(user-avatar-component [size]
|
||||
[user-avatar/user-avatar
|
||||
{:full-name "New User"
|
||||
:size size}])]
|
||||
(h/describe "Two letters"
|
||||
(h/test "Size :big"
|
||||
(h/render (user-avatar-component :big))
|
||||
(h/is-truthy (h/get-by-text "NU")))
|
||||
|
||||
(h/test "Size :medium"
|
||||
(h/render (user-avatar-component :medium))
|
||||
(h/is-truthy (h/get-by-text "NU")))
|
||||
|
||||
(h/test "Size :small"
|
||||
(h/render (user-avatar-component :small))
|
||||
(h/is-truthy (h/get-by-text "NU"))))
|
||||
|
||||
(h/describe "One letter"
|
||||
(h/test "Size :xs"
|
||||
(h/render (user-avatar-component :xs))
|
||||
(h/is-truthy (h/get-by-text "N")))
|
||||
|
||||
(h/test "Size :xxs"
|
||||
(h/render (user-avatar-component :xxs))
|
||||
(h/is-truthy (h/get-by-text "N")))
|
||||
|
||||
(h/test "Size :xxxs"
|
||||
(h/render (user-avatar-component :xxxs))
|
||||
(h/is-truthy (h/get-by-text "N"))))))
|
||||
|
||||
(h/describe "Render ring"
|
||||
(letfn [(user-avatar-component [size]
|
||||
[user-avatar/user-avatar
|
||||
{:full-name "New User"
|
||||
:ring-background mock-picture
|
||||
:size size}])]
|
||||
(h/describe "Passed and drawn"
|
||||
(h/test "Size :big"
|
||||
(h/render (user-avatar-component :big))
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-truthy (h/get-by-label-text :ring-background)))
|
||||
|
||||
(h/test "Size :medium"
|
||||
(h/render (user-avatar-component :medium))
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-truthy (h/get-by-label-text :ring-background)))
|
||||
|
||||
(h/test "Size :small"
|
||||
(h/render (user-avatar-component :small))
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-truthy (h/get-by-label-text :ring-background))))
|
||||
|
||||
(h/describe "Passed and not drawn (because of invalid size for ring)"
|
||||
(h/test "Size :xs"
|
||||
(h/render (user-avatar-component :xs))
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-null (h/query-by-label-text :ring-background)))
|
||||
|
||||
(h/test "Size :xxs"
|
||||
(h/render (user-avatar-component :xxs))
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-null (h/query-by-label-text :ring-background)))
|
||||
|
||||
(h/test "Size :xxxs"
|
||||
(h/render (user-avatar-component :xxxs))
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-null (h/query-by-label-text :ring-background))))))
|
||||
|
||||
(h/describe "Status indicator"
|
||||
(h/test "Render"
|
||||
(h/render
|
||||
[user-avatar/user-avatar
|
||||
{:full-name "Test User"
|
||||
:status-indicator? true}])
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-truthy (h/get-by-label-text :status-indicator)))
|
||||
|
||||
(h/test "Do not render"
|
||||
(h/render
|
||||
[user-avatar/user-avatar
|
||||
{:full-name "Test User"
|
||||
:status-indicator? false}])
|
||||
(h/is-truthy (h/get-by-label-text :initials-avatar))
|
||||
(h/is-null (h/query-by-label-text :status-indicator))))))
|
||||
@@ -1,83 +0,0 @@
|
||||
(ns quo2.components.avatars.user-avatar.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def sizes
|
||||
{:big {:outer 80
|
||||
:inner 72
|
||||
:status-indicator 20
|
||||
:status-indicator-border 4
|
||||
:font-size :heading-1}
|
||||
:medium {:outer 48
|
||||
:inner 44
|
||||
:status-indicator 12
|
||||
:status-indicator-border 2
|
||||
:font-size :paragraph-1}
|
||||
:small {:outer 32
|
||||
:inner 28
|
||||
:status-indicator 12
|
||||
:status-indicator-border 2
|
||||
:font-size :paragraph-2}
|
||||
:xs {:outer 24
|
||||
:inner 24
|
||||
:status-indicator 0
|
||||
:status-indicator-border 0
|
||||
:font-size :paragraph-2}
|
||||
:xxs {:outer 20
|
||||
:inner 20
|
||||
:status-indicator 0
|
||||
:status-indicator-border 0
|
||||
:font-size :label}
|
||||
:xxxs {:outer 16
|
||||
:inner 16
|
||||
:status-indicator 0
|
||||
:status-indicator-border 0
|
||||
:font-size :label}})
|
||||
|
||||
(defn outer
|
||||
[size]
|
||||
(let [dimensions (get-in sizes [size :outer])]
|
||||
{:width dimensions
|
||||
:height dimensions
|
||||
:border-radius dimensions}))
|
||||
|
||||
(defn initials-avatar
|
||||
[size draw-ring? customization-color]
|
||||
(let [outer-dimensions (get-in sizes [size :outer])
|
||||
inner-dimensions (get-in sizes [size (if draw-ring? :inner :outer)])]
|
||||
{:position :absolute
|
||||
:top (/ (- outer-dimensions inner-dimensions) 2)
|
||||
:left (/ (- outer-dimensions inner-dimensions) 2)
|
||||
:width inner-dimensions
|
||||
:height inner-dimensions
|
||||
:border-radius inner-dimensions
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color (colors/custom-color-by-theme customization-color 50 60)}))
|
||||
|
||||
(def initials-avatar-text
|
||||
{:color colors/white-opa-70})
|
||||
|
||||
(defn dot
|
||||
[size online? ring?]
|
||||
(let [background (if online? colors/success-50 colors/neutral-40)
|
||||
dimensions (get-in sizes [size :status-indicator])
|
||||
border-width (get-in sizes [size :status-indicator-border])
|
||||
right (case size
|
||||
:big 2
|
||||
:medium 0
|
||||
:small -2
|
||||
0)
|
||||
bottom (case size
|
||||
:big (if ring? -1 2)
|
||||
:medium (if ring? 0 -2)
|
||||
:small -2
|
||||
0)]
|
||||
{:position :absolute
|
||||
:bottom bottom
|
||||
:right right
|
||||
:width dimensions
|
||||
:height dimensions
|
||||
:border-width border-width
|
||||
:border-radius dimensions
|
||||
:border-color (colors/theme-colors colors/white colors/neutral-100)
|
||||
:background-color background}))
|
||||
@@ -1,61 +0,0 @@
|
||||
(ns quo2.components.avatars.user-avatar.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.avatars.user-avatar.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]))
|
||||
|
||||
(defn- extract-initials
|
||||
[full-name amount-initials]
|
||||
(let [upper-case-first-letter (comp string/upper-case first)
|
||||
names-list (string/split full-name " ")]
|
||||
(->> names-list
|
||||
(map upper-case-first-letter)
|
||||
(take amount-initials)
|
||||
(string/join))))
|
||||
|
||||
(defn initials-avatar
|
||||
[{:keys [full-name size draw-ring? customization-color]}]
|
||||
(let [font-size (get-in style/sizes [size :font-size])
|
||||
amount-initials (if (#{:xs :xxs :xxxs} size) 1 2)]
|
||||
[rn/view
|
||||
{:accessibility-label :initials-avatar
|
||||
:style (style/initials-avatar size draw-ring? customization-color)}
|
||||
[text/text
|
||||
{:style style/initials-avatar-text
|
||||
:size font-size
|
||||
:weight :semi-bold}
|
||||
(extract-initials full-name amount-initials)]]))
|
||||
|
||||
(def valid-ring-sizes #{:big :medium :small})
|
||||
|
||||
(defn user-avatar
|
||||
"If no `profile-picture` is given, draws the initials based on the `full-name` and
|
||||
uses `ring-background` to display the ring behind the initials when given. Otherwise,
|
||||
shows the `profile-picture` which already comes with the ring drawn."
|
||||
[{:keys [full-name status-indicator? online? size profile-picture ring-background
|
||||
customization-color]
|
||||
:or {status-indicator? true
|
||||
online? true
|
||||
size :big
|
||||
customization-color :turquoise}}]
|
||||
(let [full-name (or full-name "empty name")
|
||||
draw-ring? (and ring-background (valid-ring-sizes size))
|
||||
outer-styles (style/outer size)]
|
||||
[rn/view {:style outer-styles :accessibility-label :user-avatar}
|
||||
;; The `profile-picture` already has the ring in it
|
||||
(when-let [image (or profile-picture ring-background)]
|
||||
[fast-image/fast-image
|
||||
{:accessibility-label (if draw-ring? :ring-background :profile-picture)
|
||||
:style outer-styles
|
||||
:source image}])
|
||||
(when-not profile-picture
|
||||
[initials-avatar
|
||||
{:full-name full-name
|
||||
:size size
|
||||
:draw-ring? draw-ring?
|
||||
:customization-color customization-color}])
|
||||
(when status-indicator?
|
||||
[rn/view
|
||||
{:accessibility-label :status-indicator
|
||||
:style (style/dot size online? draw-ring?)}])]))
|
||||
@@ -204,7 +204,7 @@
|
||||
[button opts \"label\"]
|
||||
opts
|
||||
{:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/
|
||||
:danger/:photo-bg/:blur-bg/:blur-bg-outline/:shell/:community
|
||||
:danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell/:community
|
||||
:size 40/32/24
|
||||
:icon true/false
|
||||
:community-color '#FFFFFF'
|
||||
@@ -213,14 +213,14 @@
|
||||
:after :icon-keyword}
|
||||
|
||||
only icon
|
||||
[button {:icon true} :i/close-circle]"
|
||||
[button {:icon true} :main-icons/close-circle]"
|
||||
[_ _]
|
||||
(let [pressed (reagent/atom false)]
|
||||
(fn
|
||||
[{:keys [on-press disabled type size community-color community-text-color before after above
|
||||
width
|
||||
override-theme override-background-color
|
||||
on-long-press accessibility-label icon icon-no-color style inner-style test-ID]
|
||||
on-long-press accessibility-label icon icon-no-color style test-ID]
|
||||
:or {type :primary
|
||||
size 40}}
|
||||
children]
|
||||
@@ -275,8 +275,7 @@
|
||||
(community-themed? type community-color)
|
||||
(merge
|
||||
{:background-color community-color}
|
||||
(when (= state :pressed) {:opacity 0.9})))
|
||||
inner-style)}
|
||||
(when (= state :pressed) {:opacity 0.9}))))}
|
||||
(when above
|
||||
[rn/view
|
||||
[quo2.icons/icon above
|
||||
|
||||
@@ -78,42 +78,42 @@
|
||||
[props
|
||||
{:keys [name
|
||||
muted?
|
||||
unviewed-messages-count
|
||||
unviewed-mentions-count
|
||||
unread-messages?
|
||||
unread-mentions-count
|
||||
status
|
||||
images
|
||||
community-icon
|
||||
tokens
|
||||
locked?
|
||||
style]}]
|
||||
locked?]}]
|
||||
[rn/touchable-highlight
|
||||
(merge {:underlay-color (colors/theme-colors
|
||||
colors/neutral-5
|
||||
colors/neutral-95)
|
||||
:style {:border-radius 12}}
|
||||
props)
|
||||
[rn/view (merge (style/membership-info-container) style)
|
||||
[community-icon/community-icon
|
||||
{:images images} 32]
|
||||
[rn/view
|
||||
{:flex 1
|
||||
:margin-left 12
|
||||
:justify-content :center}
|
||||
[text/text
|
||||
{:accessibility-label :chat-name-text
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail
|
||||
:weight :semi-bold
|
||||
:size :paragraph-1}
|
||||
name]]
|
||||
[rn/view {:flex 1}
|
||||
[rn/view (style/membership-info-container)
|
||||
[community-icon/community-icon
|
||||
{:images community-icon} 32]
|
||||
[rn/view
|
||||
{:flex 1
|
||||
:margin-left 12
|
||||
:justify-content :center}
|
||||
[text/text
|
||||
{:accessibility-label :chat-name-text
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail
|
||||
:weight :semi-bold
|
||||
:size :paragraph-1}
|
||||
name]]
|
||||
|
||||
[rn/view
|
||||
{:justify-content :center
|
||||
:margin-right 16}
|
||||
(if (= status :gated)
|
||||
[community-view/permission-tag-container
|
||||
{:locked? locked?
|
||||
:tokens tokens}]
|
||||
[notification-view
|
||||
{:muted? muted?
|
||||
:unread-mentions-count unviewed-mentions-count
|
||||
:unread-messages? (pos? unviewed-messages-count)}])]]])
|
||||
[rn/view
|
||||
{:justify-content :center
|
||||
:margin-right 16}
|
||||
(if (= status :gated)
|
||||
[community-view/permission-tag-container
|
||||
{:locked? locked?
|
||||
:tokens tokens}]
|
||||
[notification-view
|
||||
{:muted? muted?
|
||||
:unread-mentions-count unread-mentions-count
|
||||
:unread-messages? unread-messages?}])]]]])
|
||||
|
||||
@@ -92,7 +92,6 @@
|
||||
colors/neutral-90)}
|
||||
{:flex-direction :row
|
||||
:margin-horizontal 20
|
||||
:margin-vertical 8
|
||||
:height 56
|
||||
:padding-right 12})
|
||||
[card-title-and-description title description]
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
:shadow-radius radius
|
||||
:shadow-opacity 1
|
||||
:shadow-color colors/shadow
|
||||
:elevation 1
|
||||
:border-radius radius
|
||||
:justify-content :space-between
|
||||
:elevation 2
|
||||
:background-color (colors/theme-colors
|
||||
colors/white
|
||||
colors/neutral-90)})
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
(ns quo2.components.counter.--tests--.counter-component-spec
|
||||
(:require [quo2.components.counter.counter :as counter]
|
||||
[test-helpers.component :as h]))
|
||||
(:require ["@testing-library/react-native" :as rtl]
|
||||
[quo2.components.counter.counter :as counter]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(h/describe "counter component"
|
||||
(h/test "default render of counter component"
|
||||
(h/render [counter/counter {} nil])
|
||||
(-> (h/expect (h/get-by-test-id :counter-component))
|
||||
(h/is-truthy)))
|
||||
(defn render-counter
|
||||
([]
|
||||
(render-counter {} nil))
|
||||
([opts value]
|
||||
(rtl/render (reagent/as-element [counter/counter opts value]))))
|
||||
|
||||
(h/test "renders counter with a string value"
|
||||
(h/render [counter/counter {} "1"])
|
||||
(-> (h/expect (h/get-by-text "1"))
|
||||
(h/is-truthy)))
|
||||
(js/global.test "default render of counter component"
|
||||
(fn []
|
||||
(render-counter)
|
||||
(-> (js/expect (rtl/screen.getByTestId "counter-component"))
|
||||
(.toBeTruthy))))
|
||||
|
||||
(h/test "renders counter with an integer value"
|
||||
(h/render [counter/counter {} 1])
|
||||
(-> (h/expect (h/get-by-text "1"))
|
||||
(h/is-truthy)))
|
||||
(js/global.test "renders counter with a string value"
|
||||
(fn []
|
||||
(render-counter {} "1")
|
||||
(-> (js/expect (rtl/screen.getByText "1"))
|
||||
(.toBeTruthy))))
|
||||
|
||||
(h/test "renders counter with max value 99+ by default"
|
||||
(h/render [counter/counter {} 100])
|
||||
(-> (h/expect (h/get-by-text "99+"))
|
||||
(h/is-truthy)))
|
||||
(js/global.test "renders counter with an integer value"
|
||||
(fn []
|
||||
(render-counter {} 1)
|
||||
(-> (js/expect (rtl/screen.getByText "1"))
|
||||
(.toBeTruthy))))
|
||||
|
||||
(h/test "renders counter with custom max value when set to 150"
|
||||
(h/render [counter/counter {:max-value 150} 151])
|
||||
(-> (h/expect (h/get-by-text "150+"))
|
||||
(h/is-truthy))))
|
||||
(js/global.test "renders counter with value 99+ when the value is greater than 99"
|
||||
(fn []
|
||||
(render-counter {} "100")
|
||||
(-> (js/expect (rtl/screen.getByText "99+"))
|
||||
(.toBeTruthy))))
|
||||
@@ -1,10 +1,8 @@
|
||||
(ns quo2.components.counter.counter
|
||||
(:require
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[utils.number :as utils-number]))
|
||||
(:require [quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(def themes
|
||||
{:light {:default colors/primary-50
|
||||
@@ -21,9 +19,9 @@
|
||||
(get-in themes [(theme/get-theme) key]))
|
||||
|
||||
(defn counter
|
||||
[{:keys [type override-text-color override-bg-color style accessibility-label max-value]
|
||||
:or {max-value 99}}
|
||||
value]
|
||||
"type: default, secondary, grey, outline
|
||||
value: integer"
|
||||
[{:keys [type override-text-color override-bg-color style accessibility-label]} value]
|
||||
(let [type (or type :default)
|
||||
text-color (or override-text-color
|
||||
(if (or
|
||||
@@ -31,9 +29,11 @@
|
||||
(= type :default))
|
||||
colors/white
|
||||
colors/neutral-100))
|
||||
value (utils-number/parse-int value)
|
||||
label (if (> value max-value)
|
||||
(str max-value "+")
|
||||
value (if (integer? value)
|
||||
value
|
||||
(js/parseInt value))
|
||||
label (if (> value 99)
|
||||
"99+"
|
||||
(str value))
|
||||
width (case (count label)
|
||||
1 16
|
||||
@@ -59,7 +59,7 @@
|
||||
(or override-bg-color
|
||||
(get-color type)))
|
||||
|
||||
(> value max-value)
|
||||
(> value 99)
|
||||
(assoc :padding-left 0.5))}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn apply-anim
|
||||
[dd-height val]
|
||||
(reanimated/animate-shared-value-with-delay dd-height
|
||||
val
|
||||
300
|
||||
:easing1
|
||||
0))
|
||||
(ra/animate-delay dd-height
|
||||
val
|
||||
0
|
||||
300
|
||||
:easing1))
|
||||
|
||||
(def sizes
|
||||
{:big {:icon-size 20
|
||||
@@ -141,7 +141,7 @@
|
||||
[:f>
|
||||
(fn []
|
||||
(let [open? (reagent/atom false)
|
||||
dd-height (reanimated/use-shared-value 0)]
|
||||
dd-height (ra/use-val 0)]
|
||||
[rn/view {:style {:flex-grow 1}}
|
||||
[dropdown-comp
|
||||
{:items items
|
||||
@@ -155,8 +155,8 @@
|
||||
:default-item default-item
|
||||
:open? open?
|
||||
:dd-height dd-height}]
|
||||
[reanimated/view
|
||||
{:style (reanimated/apply-animations-to-style
|
||||
[ra/view
|
||||
{:style (ra/apply-animations-to-style
|
||||
{:height dd-height}
|
||||
{:height dd-height})}
|
||||
[items-comp
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def header-height 56)
|
||||
@@ -162,7 +162,7 @@
|
||||
border-bottom false}}]
|
||||
(let [status-bar-height (get insets :top 0)
|
||||
height (+ header-height status-bar-height)]
|
||||
[reanimated/view
|
||||
[ra/view
|
||||
{:style (header-wrapper-style {:height height
|
||||
:background background
|
||||
:border-bottom border-bottom})}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns quo2.components.inputs.input.style
|
||||
(ns quo2.components.input.style
|
||||
(:require [quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns quo2.components.inputs.input.view
|
||||
(ns quo2.components.input.view
|
||||
(:require [oops.core :as oops]
|
||||
[quo2.components.icon :as icon]
|
||||
[quo2.components.inputs.input.style :as style]
|
||||
[quo2.components.input.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
@@ -1,29 +0,0 @@
|
||||
(ns quo2.components.inputs.profile-input.component-spec
|
||||
(:require [quo2.components.inputs.profile-input.view :as profile-input]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "Profile Input"
|
||||
(h/test "renders user avatar with placeholder name if no value is specified"
|
||||
(h/render [profile-input/profile-input
|
||||
{:placeholder "Your Name"}])
|
||||
(-> (js/expect (h/get-by-text "YN"))
|
||||
(.toBeTruthy)))
|
||||
|
||||
(h/test "renders user avatar with full name if a value is specified"
|
||||
(let [event (h/mock-fn)]
|
||||
(h/render [profile-input/profile-input
|
||||
{:on-change event
|
||||
:placeholder "Your Name"
|
||||
:image-picker-props {:full-name "Test Name"}}])
|
||||
(h/fire-event :change (h/get-by-text "TN"))
|
||||
(-> (js/expect (h/get-by-text "TN"))
|
||||
(.toBeTruthy))))
|
||||
|
||||
(h/test "on press event fires"
|
||||
(let [event (h/mock-fn)]
|
||||
(h/render [profile-input/profile-input
|
||||
{:placeholder "Your Name"
|
||||
:on-press event}])
|
||||
(h/fire-event :press (h/get-by-label-text :select-profile-picture-button))
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1)))))
|
||||
@@ -1,29 +0,0 @@
|
||||
(ns quo2.components.inputs.profile-input.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn container
|
||||
[customization-color]
|
||||
{:background-color (colors/custom-color customization-color 50 40)
|
||||
:padding-horizontal 12
|
||||
:padding-top 12
|
||||
:padding-bottom 10
|
||||
:border-radius 16
|
||||
:height 102
|
||||
:flex 1})
|
||||
|
||||
(def button
|
||||
{:position :absolute
|
||||
:top 23
|
||||
:bottom 0
|
||||
:left 33
|
||||
:right 0
|
||||
:width 24
|
||||
:height 24
|
||||
:border-radius 24})
|
||||
|
||||
(def button-inner {:border-radius 24})
|
||||
|
||||
(def input-container
|
||||
{:flex-direction :row
|
||||
:margin-top 4
|
||||
:align-items :center})
|
||||
@@ -1,51 +0,0 @@
|
||||
(ns quo2.components.inputs.profile-input.view
|
||||
(:require
|
||||
[quo2.components.buttons.button :as buttons]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.inputs.profile-input.style :as style]
|
||||
[quo2.components.inputs.title-input.view :as title-input]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.hole-view :as hole-view]))
|
||||
|
||||
(defn profile-input
|
||||
[{:keys [customization-color
|
||||
placeholder
|
||||
on-press
|
||||
title-input-props
|
||||
image-picker-props]}]
|
||||
(let [full-name (:full-name image-picker-props)]
|
||||
[rn/view
|
||||
{:style (style/container customization-color)}
|
||||
[rn/view
|
||||
[hole-view/hole-view
|
||||
{:holes [{:x 33
|
||||
:y 23
|
||||
:width 24
|
||||
:height 24
|
||||
:borderRadius 12}]}
|
||||
[user-avatar/user-avatar
|
||||
(merge image-picker-props
|
||||
{:customization-color customization-color
|
||||
:full-name (if (seq full-name)
|
||||
full-name
|
||||
placeholder)
|
||||
:status-indicator? false
|
||||
:size :medium})]]
|
||||
[buttons/button
|
||||
{:accessibility-label :select-profile-picture-button
|
||||
:type :grey
|
||||
:override-theme :dark
|
||||
:override-background-color (colors/alpha colors/white 0.05)
|
||||
:on-press on-press
|
||||
:icon-size 20
|
||||
:width 24
|
||||
:size 24
|
||||
:icon :i/camera
|
||||
:style style/button
|
||||
:inner-style style/button-inner} :i/camera]]
|
||||
[rn/view {:style style/input-container}
|
||||
[title-input/title-input
|
||||
(merge title-input-props
|
||||
{:placeholder placeholder
|
||||
:customization-color customization-color})]]]))
|
||||
@@ -1,40 +0,0 @@
|
||||
(ns quo2.components.inputs.title-input.component-spec
|
||||
(:require [quo2.components.inputs.title-input.view :as title-input]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "profile: title-input"
|
||||
(h/test "renders empty"
|
||||
(h/render [title-input/title-input
|
||||
{:value ""
|
||||
:on-change-text (h/mock-fn)}])
|
||||
(-> (js/expect (h/get-by-label-text :profile-title-input))
|
||||
(.toBeTruthy)))
|
||||
|
||||
(h/test "empty text renders with max length digits and 00"
|
||||
(h/render [title-input/title-input
|
||||
{:value ""
|
||||
:max-length 24
|
||||
:on-change-text (h/mock-fn)}])
|
||||
(-> (js/expect (h/get-by-text "00"))
|
||||
(.toBeTruthy))
|
||||
(-> (js/expect (h/get-by-text "/24"))
|
||||
(.toBeTruthy)))
|
||||
|
||||
(h/test "renders with max length digits and character count"
|
||||
(h/render [title-input/title-input
|
||||
{:default-value "abc"
|
||||
:max-length 24
|
||||
:on-change-text (h/mock-fn)} "abc"])
|
||||
(-> (js/expect (h/get-by-text "03"))
|
||||
(.toBeTruthy))
|
||||
(-> (js/expect (h/get-by-text "/24"))
|
||||
(.toBeTruthy)))
|
||||
|
||||
(h/test "text updates on change"
|
||||
(let [on-change-text (h/mock-fn)]
|
||||
(h/render [title-input/title-input
|
||||
{:value "mock text"
|
||||
:on-change-text on-change-text}])
|
||||
(h/fire-event :change-text (h/get-by-label-text :profile-title-input) "mock-text-new")
|
||||
(-> (js/expect on-change-text)
|
||||
(.toHaveBeenCalled)))))
|
||||
@@ -1,52 +0,0 @@
|
||||
(ns quo2.components.inputs.title-input.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn get-focused-placeholder-color
|
||||
[blur?]
|
||||
(if blur?
|
||||
(colors/theme-colors colors/neutral-80-opa-20 (colors/alpha colors/white 0.2))
|
||||
(colors/theme-colors colors/neutral-30 colors/neutral-60)))
|
||||
|
||||
(defn get-placeholder-color
|
||||
[blur?]
|
||||
(if blur?
|
||||
(colors/theme-colors colors/neutral-80-opa-40 (colors/alpha colors/white 0.3))
|
||||
(colors/theme-colors colors/neutral-40 colors/neutral-50)))
|
||||
|
||||
(defn- get-disabled-color
|
||||
[blur?]
|
||||
(if blur?
|
||||
(colors/theme-colors colors/neutral-80-opa-40 (colors/alpha colors/white 0.3))
|
||||
(colors/theme-colors colors/neutral-40 colors/neutral-50)))
|
||||
|
||||
(defn- get-char-count-color
|
||||
[blur?]
|
||||
(if blur?
|
||||
(colors/theme-colors colors/neutral-80-opa-40 (colors/alpha colors/white 0.4))
|
||||
(colors/theme-colors colors/neutral-40 colors/neutral-50)))
|
||||
|
||||
(defn get-selection-color
|
||||
[customization-color blur?]
|
||||
(if blur?
|
||||
(colors/theme-colors colors/neutral-100 colors/white)
|
||||
(colors/custom-color customization-color (if colors/dark? 60 50))))
|
||||
|
||||
(def text-input-container {:flex 1})
|
||||
|
||||
(defn title-text
|
||||
[disabled? blur?]
|
||||
{:text-align-vertical :bottom
|
||||
:color (when disabled? (get-disabled-color blur?))})
|
||||
|
||||
(defn char-count
|
||||
[blur?]
|
||||
{:color (get-char-count-color blur?)})
|
||||
|
||||
(def container
|
||||
{:flex-direction :row
|
||||
:flex 1
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def counter-container
|
||||
{:padding-top 8})
|
||||
@@ -1,65 +0,0 @@
|
||||
(ns quo2.components.inputs.title-input.view
|
||||
(:require
|
||||
[quo2.components.inputs.title-input.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[reagent.core :as reagent]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn- pad-0
|
||||
[value]
|
||||
(if (<= (count value) 1)
|
||||
(str 0 value)
|
||||
value))
|
||||
|
||||
(defn title-input
|
||||
[{:keys [blur?
|
||||
on-change-text
|
||||
placeholder
|
||||
max-length
|
||||
default-value]
|
||||
:or {max-length 0
|
||||
default-value ""}}]
|
||||
(let [focused? (reagent/atom false)
|
||||
value (reagent/atom default-value)
|
||||
on-change (fn [v]
|
||||
(reset! value v)
|
||||
(when on-change-text
|
||||
(on-change-text v)))]
|
||||
(fn [{:keys [customization-color disabled?]}]
|
||||
[rn/view
|
||||
{:style style/container}
|
||||
[rn/view {:style style/text-input-container}
|
||||
[rn/text-input
|
||||
{:style
|
||||
(text/text-style
|
||||
{:size :heading-2
|
||||
:weight :semi-bold
|
||||
:style (style/title-text disabled? blur?)})
|
||||
:default-value default-value
|
||||
:accessibility-label :profile-title-input
|
||||
:on-focus #(swap! focused? (fn [] true))
|
||||
:on-blur #(swap! focused? (fn [] false))
|
||||
:input-mode :text
|
||||
:on-change-text on-change
|
||||
:editable (not disabled?)
|
||||
:max-length max-length
|
||||
:placeholder placeholder
|
||||
:selection-color (style/get-selection-color customization-color blur?)
|
||||
:placeholder-text-color (if @focused?
|
||||
(style/get-focused-placeholder-color blur?)
|
||||
(style/get-placeholder-color blur?))}]]
|
||||
[rn/view
|
||||
{:style style/counter-container}
|
||||
[text/text
|
||||
[text/text
|
||||
{:style (style/char-count blur?)
|
||||
:size :paragraph-2}
|
||||
(pad-0
|
||||
(str
|
||||
(count @value)))]
|
||||
[text/text
|
||||
{:style (style/char-count blur?)
|
||||
:size :paragraph-2}
|
||||
(str "/"
|
||||
(pad-0
|
||||
(str max-length)))]]]])))
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns quo2.components.list-items.preview-list
|
||||
(:require [quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
(:require [quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[quo2.components.icon :as quo2.icons]
|
||||
[quo2.components.markdown.text :as quo2.text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.list-items.user-list
|
||||
(:require [react-native.core :as rn]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.foundations.colors :as colors]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.masked-view :as masked-view]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def message-skeleton-height 54)
|
||||
@@ -30,12 +30,12 @@
|
||||
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
|
||||
translate-x (ra/use-val (- window-width))
|
||||
animated-gradient-style (ra/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)
|
||||
(ra/animate-repeat translate-x window-width (- 1) false 1000 :linear)
|
||||
[masked-view/masked-view
|
||||
{:style {:height message-skeleton-height}
|
||||
:maskElement (reagent/as-element
|
||||
@@ -70,7 +70,7 @@
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:background-color color}}
|
||||
[reanimated/linear-gradient
|
||||
[ra/linear-gradient
|
||||
{:colors [color color loading-color color color]
|
||||
:start {:x 0 :y 0}
|
||||
:end {:x 1 :y 0}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
(ns quo2.components.messages.system-message
|
||||
(:require [quo2.components.avatars.icon-avatar :as icon-avatar]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[utils.string :as utils]))
|
||||
|
||||
(def themes-landed
|
||||
@@ -176,20 +176,20 @@
|
||||
[{:keys [type style non-pressable? animate-landing? labels on-long-press] :as message}]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [sv-color (reanimated/use-shared-value
|
||||
(let [sv-color (ra/use-val
|
||||
(get-color :bg (if animate-landing? :landed :default) type))]
|
||||
(when animate-landing?
|
||||
(reanimated/animate-shared-value-with-delay
|
||||
(ra/animate-delay
|
||||
sv-color
|
||||
(get-color :bg :default type)
|
||||
1000
|
||||
0
|
||||
:linear
|
||||
1000))
|
||||
[reanimated/touchable-opacity
|
||||
:linear))
|
||||
[ra/touchable-opacity
|
||||
{:on-press #(when-not non-pressable?
|
||||
(reanimated/set-shared-value sv-color (get-color :bg :pressed type)))
|
||||
(ra/set-val sv-color (get-color :bg :pressed type)))
|
||||
:on-long-press on-long-press
|
||||
:style (reanimated/apply-animations-to-style
|
||||
:style (ra/apply-animations-to-style
|
||||
{:background-color sv-color}
|
||||
(merge
|
||||
{:flex-direction :row
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.hole-view :as hole-view]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
[react-native.reanimated :as ra]))
|
||||
|
||||
(defn toggle-background-color
|
||||
[background-color press-out? pass-through?]
|
||||
(reanimated/set-shared-value
|
||||
(ra/set-val
|
||||
background-color
|
||||
(cond
|
||||
press-out? "transparent"
|
||||
@@ -31,14 +31,14 @@
|
||||
on-press pass-through? icon-color-anim accessibility-label test-ID]}]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [icon-animated-style (reanimated/apply-animations-to-style
|
||||
(let [icon-animated-style (ra/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 (ra/use-val "transparent")
|
||||
background-animated-style (ra/apply-animations-to-style
|
||||
{:background-color background-color}
|
||||
{:width 90
|
||||
:height 40
|
||||
@@ -49,7 +49,7 @@
|
||||
: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}
|
||||
[ra/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
|
||||
@@ -69,10 +69,10 @@
|
||||
|
||||
:else
|
||||
[{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])}
|
||||
[reanimated/image
|
||||
[ra/image
|
||||
{:style icon-animated-style
|
||||
:source (icons/icon-source (keyword (str icon 24)))}]]
|
||||
[reanimated/image
|
||||
[ra/image
|
||||
{:style icon-animated-style
|
||||
:source (icons/icon-source (keyword (str icon 24)))}])
|
||||
(when new-notifications?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns quo2.components.navigation.floating-shell-button
|
||||
(:require [quo2.components.buttons.dynamic-button :as dynamic-button]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
[react-native.reanimated :as ra]))
|
||||
|
||||
(defn dynamic-button-view
|
||||
[type dynamic-buttons style]
|
||||
@@ -28,12 +28,12 @@
|
||||
:margin-horizontal 12
|
||||
:pointer-events :box-none}
|
||||
style)
|
||||
animated-style (reanimated/apply-animations-to-style
|
||||
animated-style (ra/apply-animations-to-style
|
||||
(if opacity-anim
|
||||
{:opacity opacity-anim}
|
||||
{})
|
||||
original-style)]
|
||||
[reanimated/view {:style animated-style}
|
||||
[ra/view {:style animated-style}
|
||||
;; Left Section
|
||||
[rn/view {:style {:flex 1}}
|
||||
[dynamic-button-view :search dynamic-buttons
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.navigation.page-nav
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
@@ -30,7 +30,7 @@
|
||||
{:no-color true})))
|
||||
|
||||
(defn left-section-view
|
||||
[{:keys [on-press icon accessibility-label type icon-override-theme] :or {type :grey}}
|
||||
[{:keys [on-press icon accessibility-label type] :or {type :grey}}
|
||||
put-middle-section-on-left?]
|
||||
[rn/view {:style (when put-middle-section-on-left? {:margin-right 5})}
|
||||
[button/button
|
||||
@@ -38,8 +38,7 @@
|
||||
:icon true
|
||||
:type type
|
||||
:size 32
|
||||
:accessibility-label accessibility-label
|
||||
:override-theme icon-override-theme}
|
||||
:accessibility-label accessibility-label}
|
||||
icon]])
|
||||
|
||||
(defn- mid-section-comp
|
||||
@@ -149,20 +148,10 @@
|
||||
:flex-direction :row
|
||||
: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]
|
||||
:or {type :grey}}]
|
||||
(map-indexed (fn [index {:keys [icon on-press type style] :or {type :grey}}]
|
||||
^{:key index}
|
||||
[rn/view
|
||||
{:style (assoc style
|
||||
:margin-right
|
||||
(if (= index last-icon-index) 0 8))}
|
||||
[button/button
|
||||
{:on-press on-press
|
||||
:icon true
|
||||
:type type
|
||||
:size 32
|
||||
:override-theme icon-override-theme}
|
||||
[rn/view {:style (merge {:margin-right (if (not= index last-icon-index) 8 0)} style)}
|
||||
[button/button {:on-press on-press :icon true :type type :size 32}
|
||||
icon]])
|
||||
right-section-buttons))])
|
||||
|
||||
@@ -190,14 +179,16 @@
|
||||
:left-section
|
||||
{:type button-type
|
||||
:on-press event
|
||||
:icon icon
|
||||
:icon-override-theme :light/:dark
|
||||
:icon icon
|
||||
:icon-color color
|
||||
:icon-background-color color
|
||||
}
|
||||
:right-section-buttons vector of
|
||||
{:type button-type
|
||||
:on-press event
|
||||
:icon icon
|
||||
:icon-override-theme :light/:dark
|
||||
:icon-color color
|
||||
:icon-background-color color
|
||||
}
|
||||
}
|
||||
"
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
[quo2.components.notifications.activity-log.style :as style]
|
||||
[quo2.components.tags.status-tags :as status-tags]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[utils.i18n :as i18n]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[utils.i18n :as i18n]))
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn- activity-reply-text-input
|
||||
[{:keys [on-update-reply max-reply-length valid-reply?]} reply-input]
|
||||
@@ -37,7 +37,7 @@
|
||||
(when on-update-reply
|
||||
(on-update-reply %)))
|
||||
:auto-capitalize :none
|
||||
:auto-focus false
|
||||
:auto-focus true
|
||||
:accessibility-label :identity-verification-reply-text-input
|
||||
:placeholder (i18n/label :t/type-something)
|
||||
:return-key-type :none
|
||||
@@ -129,20 +129,19 @@
|
||||
(-> button
|
||||
(assoc :size size)
|
||||
(assoc :type subtype)
|
||||
(assoc :disabled (and replying? disable-when (disable-when @reply-input)))
|
||||
(assoc :disabled (and replying? (disable-when @reply-input)))
|
||||
(update :style merge common-style {:margin-right 8}))
|
||||
label]))
|
||||
|
||||
(defmethod footer-item-view :status
|
||||
[{:keys [label subtype blur?]} _ _]
|
||||
[{:keys [label subtype]} _ _]
|
||||
[status-tags/status-tag
|
||||
{:size :small
|
||||
:label label
|
||||
:status {:type subtype}
|
||||
:blur? blur?}])
|
||||
:status {:type subtype}}])
|
||||
|
||||
(defn- footer
|
||||
[_]
|
||||
[_ _]
|
||||
(let [reply-input (reagent/atom "")]
|
||||
(fn [{:keys [replying? items] :as props}]
|
||||
[:<>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
(ns quo2.components.onboarding.small-option-card.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def main-variant-height 335)
|
||||
(def icon-variant-height 56)
|
||||
|
||||
(def text-container {:flex 1})
|
||||
|
||||
(def title
|
||||
@@ -44,10 +41,12 @@
|
||||
|
||||
(def touchable-overlay {:border-radius 16})
|
||||
|
||||
(defn card-container
|
||||
[main-variant?]
|
||||
(when main-variant? {:height 343}))
|
||||
|
||||
(defn card
|
||||
[height]
|
||||
[main-variant?]
|
||||
{:background-color colors/white-opa-5
|
||||
:border-radius 16
|
||||
:height height})
|
||||
|
||||
(def main-variant-extra-space {:height 8})
|
||||
:height (if main-variant? 335 56)})
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
:subtitle subtitle}]])
|
||||
|
||||
(defn- main-variant
|
||||
[{:keys [title subtitle image max-height]}]
|
||||
[{:keys [title subtitle image]}]
|
||||
[rn/view {:style style/main-variant}
|
||||
[rn/view {:style style/main-variant-text-container}
|
||||
[texts
|
||||
@@ -43,32 +43,24 @@
|
||||
:subtitle subtitle}]]
|
||||
[fast-image/fast-image
|
||||
{:accessibility-label :small-option-card-main-image
|
||||
:style {:flex 1 :max-height max-height}
|
||||
:style {:flex 1}
|
||||
:resize-mode :contain
|
||||
:source image}]])
|
||||
|
||||
(defn small-option-card
|
||||
"Variants: `:main` or `:icon`"
|
||||
[{:keys [variant title subtitle image max-height on-press]
|
||||
[{:keys [variant title subtitle image on-press]
|
||||
:or {variant :main}}]
|
||||
(let [main-variant? (= variant :main)
|
||||
card-component (if main-variant? main-variant icon-variant)
|
||||
card-height (cond
|
||||
(not main-variant?) style/icon-variant-height
|
||||
max-height (min max-height style/main-variant-height)
|
||||
:else style/main-variant-height)]
|
||||
[rn/view
|
||||
[rn/touchable-highlight
|
||||
{:accessibility-label :small-option-card
|
||||
:style style/touchable-overlay
|
||||
:active-opacity 1
|
||||
:underlay-color colors/white-opa-5
|
||||
:on-press on-press}
|
||||
[rn/view {:style (style/card card-height)}
|
||||
card-component (if main-variant? main-variant icon-variant)]
|
||||
[rn/touchable-highlight
|
||||
{:accessibility-label :small-option-card
|
||||
:style style/touchable-overlay
|
||||
:active-opacity 1
|
||||
:underlay-color colors/white-opa-5
|
||||
:on-press on-press}
|
||||
[rn/view {:style (style/card-container main-variant?)}
|
||||
[rn/view {:style (style/card main-variant?)}
|
||||
[card-component
|
||||
{:title title
|
||||
:subtitle subtitle
|
||||
:image image
|
||||
:max-height max-height}]]]
|
||||
(when main-variant?
|
||||
[rn/view {:style style/main-variant-extra-space}])]))
|
||||
{:title title
|
||||
:subtitle subtitle
|
||||
:image image}]]]]))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[quo2.components.profile.profile-card.style :as style]))
|
||||
|
||||
(defn profile-card
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[quo2.components.profile.select-profile.style :as style]
|
||||
[react-native.core :as rn]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn- on-change-handler
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 0}})
|
||||
:locationY 70}})
|
||||
(-> (h/expect event)
|
||||
(.toHaveBeenCalledTimes 1))))
|
||||
|
||||
@@ -37,23 +36,19 @@
|
||||
(h/render [record-audio/record-audio
|
||||
{:on-reviewing-audio event
|
||||
:record-audio-permission-granted true}])
|
||||
(with-redefs [audio/start-recording (fn [_ on-start _]
|
||||
(on-start))
|
||||
audio/get-recorder-file-path (fn [] "file-path")]
|
||||
(with-redefs [audio/start-recording (fn [_ on-start _]
|
||||
(on-start))]
|
||||
(h/fire-event
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 0}})
|
||||
:locationY 70}})
|
||||
(h/advance-timers-by-time 500)
|
||||
(h/fire-event
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 200}})
|
||||
(h/advance-timers-by-time 250)
|
||||
:locationY 70}})
|
||||
(-> (h/expect event)
|
||||
(.toHaveBeenCalledTimes 1)))))
|
||||
|
||||
@@ -64,40 +59,27 @@
|
||||
:record-audio-permission-granted true}])
|
||||
(with-redefs [audio/start-recording (fn [_ on-start _]
|
||||
(on-start))
|
||||
audio/get-recorder-file-path (fn [] "audio-file-path")
|
||||
audio/get-player-duration (fn [] 5000)]
|
||||
audio/get-recorder-file-path (fn [] "audio-file-path")]
|
||||
(h/fire-event
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 0}})
|
||||
:locationY 70}})
|
||||
(h/advance-timers-by-time 500)
|
||||
(h/fire-event
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 200}})
|
||||
(h/fire-event
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 0}})
|
||||
(h/advance-timers-by-time 500)
|
||||
:locationY 70}})
|
||||
(h/fire-event
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 80
|
||||
:locationY 80
|
||||
:timestamp 200}})
|
||||
(h/advance-timers-by-time 250)
|
||||
:locationY 80}})
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1))
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledWith {:file-path "audio-file-path"
|
||||
:duration 5000})))))
|
||||
(.toHaveBeenCalledWith "audio-file-path")))))
|
||||
|
||||
(h/test "record-audio on-send works after sliding to the send button"
|
||||
(let [event (js/jest.fn)]
|
||||
@@ -113,8 +95,7 @@
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 0}})
|
||||
:locationY 70}})
|
||||
(h/advance-timers-by-time 500)
|
||||
(h/fire-event
|
||||
:on-responder-move
|
||||
@@ -127,14 +108,11 @@
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 40
|
||||
:locationY 80
|
||||
:timestamp 200}})
|
||||
(h/advance-timers-by-time 250)
|
||||
:locationY 80}})
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1))
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledWith {:file-path "audio-file-path"
|
||||
:duration 500})))))
|
||||
(.toHaveBeenCalledWith "audio-file-path")))))
|
||||
|
||||
(h/test "record-audio on-cancel works after reviewing audio"
|
||||
(let [event (js/jest.fn)]
|
||||
@@ -147,22 +125,18 @@
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 0}})
|
||||
:locationY 70}})
|
||||
(h/advance-timers-by-time 500)
|
||||
(h/fire-event
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 200}})
|
||||
:locationY 70}})
|
||||
(h/fire-event
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 40
|
||||
:locationY 80
|
||||
:timestamp 200}})
|
||||
(h/advance-timers-by-time 250)
|
||||
:locationY 80}})
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1)))))
|
||||
|
||||
@@ -179,8 +153,7 @@
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX 70
|
||||
:locationY 70
|
||||
:timestamp 0}})
|
||||
:locationY 70}})
|
||||
(h/advance-timers-by-time 500)
|
||||
(h/fire-event
|
||||
:on-responder-move
|
||||
@@ -193,8 +166,6 @@
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
{:nativeEvent {:locationX -10
|
||||
:locationY 70
|
||||
:timestamp 200}})
|
||||
(h/advance-timers-by-time 250)
|
||||
:locationY 70}})
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1))))))
|
||||
|
||||
@@ -2,62 +2,60 @@
|
||||
(:require [quo2.components.icon :as icons]
|
||||
[quo2.components.record-audio.record-audio.style :as style]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[react-native.core :refer [use-effect]]
|
||||
[quo2.components.record-audio.record-audio.helpers :as helpers]))
|
||||
[quo2.components.record-audio.record-audio.helpers :refer
|
||||
[animate-linear-with-delay
|
||||
animate-easing-with-delay
|
||||
animate-linear
|
||||
set-value]]))
|
||||
|
||||
(defn delete-button
|
||||
[recording? ready-to-delete? reviewing-audio? force-show-controls?]
|
||||
[recording? ready-to-delete? reviewing-audio?]
|
||||
[: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)
|
||||
(let [opacity (ra/use-val 0)
|
||||
translate-x (ra/use-val 20)
|
||||
scale (ra/use-val 1)
|
||||
connector-opacity (ra/use-val 0)
|
||||
connector-width (ra/use-val 24)
|
||||
connector-height (ra/use-val 12)
|
||||
border-radius-first-half (ra/use-val 8)
|
||||
border-radius-second-half (ra/use-val 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))
|
||||
(animate-linear-with-delay translate-x 12 50 133.33)
|
||||
(animate-easing-with-delay connector-opacity 1 0 93.33)
|
||||
(animate-easing-with-delay connector-width 56 83.33 80)
|
||||
(animate-easing-with-delay connector-height 56 83.33 80)
|
||||
(animate-easing-with-delay border-radius-first-half 28 83.33 80)
|
||||
(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))
|
||||
(animate-linear translate-x 0 100)
|
||||
(set-value connector-opacity 0)
|
||||
(set-value connector-width 24)
|
||||
(set-value connector-height 12)
|
||||
(set-value border-radius-first-half 8)
|
||||
(set-value border-radius-second-half 16))
|
||||
fade-in-animation (fn []
|
||||
(helpers/animate-linear translate-x 0 200)
|
||||
(helpers/animate-linear opacity 1 200))
|
||||
(animate-linear translate-x 0 200)
|
||||
(animate-linear opacity 1 200))
|
||||
fade-out-animation (fn []
|
||||
(helpers/animate-linear
|
||||
(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))
|
||||
(animate-linear scale 0.75 200)
|
||||
(animate-linear opacity 0 200))
|
||||
(set-value connector-opacity 0)
|
||||
(set-value connector-width 24)
|
||||
(set-value connector-height 12)
|
||||
(set-value border-radius-first-half 8)
|
||||
(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))]
|
||||
(animate-linear opacity 0 200)
|
||||
(animate-linear-with-delay translate-x 20 0 200)
|
||||
(animate-linear-with-delay scale 1 0 200))]
|
||||
(use-effect (fn []
|
||||
(if @recording?
|
||||
(fade-in-animation)
|
||||
@@ -75,14 +73,14 @@
|
||||
(reset-x-animation)))
|
||||
[@ready-to-delete?])
|
||||
[:<>
|
||||
[reanimated/view {:style (style/delete-button-container opacity)}
|
||||
[reanimated/view
|
||||
[ra/view {:style (style/delete-button-container opacity)}
|
||||
[ra/view
|
||||
{:style (style/delete-button-connector connector-opacity
|
||||
connector-width
|
||||
connector-height
|
||||
border-radius-first-half
|
||||
border-radius-second-half)}]]
|
||||
[reanimated/view
|
||||
[ra/view
|
||||
{:style (style/delete-button scale translate-x opacity)
|
||||
:pointer-events :none}
|
||||
[icons/icon :i/delete
|
||||
|
||||
@@ -2,52 +2,56 @@
|
||||
(:require [quo2.components.icon :as icons]
|
||||
[quo2.components.record-audio.record-audio.style :as style]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[react-native.core :refer [use-effect]]
|
||||
[quo2.components.record-audio.record-audio.helpers :as helpers]))
|
||||
[quo2.components.record-audio.record-audio.helpers :refer
|
||||
[animate-linear-with-delay
|
||||
animate-easing-with-delay
|
||||
animate-linear
|
||||
set-value]]))
|
||||
|
||||
(defn lock-button
|
||||
[recording? ready-to-lock? locked?]
|
||||
[: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)
|
||||
(let [translate-x-y (ra/use-val 20)
|
||||
opacity (ra/use-val 0)
|
||||
connector-opacity (ra/use-val 0)
|
||||
width (ra/use-val 24)
|
||||
height (ra/use-val 12)
|
||||
border-radius-first-half (ra/use-val 8)
|
||||
border-radius-second-half (ra/use-val 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))
|
||||
(animate-linear-with-delay translate-x-y 8 50 116.66)
|
||||
(animate-easing-with-delay connector-opacity 1 0 80)
|
||||
(animate-easing-with-delay width 56 83.33 63.33)
|
||||
(animate-easing-with-delay height 56 83.33 63.33)
|
||||
(animate-easing-with-delay border-radius-first-half
|
||||
28
|
||||
83.33
|
||||
63.33)
|
||||
(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))
|
||||
(animate-linear translate-x-y 0 100)
|
||||
(set-value connector-opacity 0)
|
||||
(set-value width 24)
|
||||
(set-value height 12)
|
||||
(set-value border-radius-first-half 8)
|
||||
(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))
|
||||
(animate-linear translate-x-y 0 220)
|
||||
(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))]
|
||||
(animate-linear translate-x-y 20 200)
|
||||
(animate-linear opacity 0 200)
|
||||
(set-value connector-opacity 0)
|
||||
(set-value width 24)
|
||||
(set-value height 12)
|
||||
(set-value border-radius-first-half 8)
|
||||
(set-value border-radius-second-half 16))]
|
||||
(use-effect (fn []
|
||||
(if @recording?
|
||||
(fade-in-animation)
|
||||
@@ -66,14 +70,14 @@
|
||||
(reset-x-y-animation)))
|
||||
[@locked?])
|
||||
[:<>
|
||||
[reanimated/view {:style (style/lock-button-container opacity)}
|
||||
[reanimated/view
|
||||
[ra/view {:style (style/lock-button-container opacity)}
|
||||
[ra/view
|
||||
{:style (style/lock-button-connector connector-opacity
|
||||
width
|
||||
height
|
||||
border-radius-first-half
|
||||
border-radius-second-half)}]]
|
||||
[reanimated/view
|
||||
[ra/view
|
||||
{:style (style/lock-button translate-x-y opacity)
|
||||
:pointer-events :none}
|
||||
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
|
||||
|
||||
@@ -3,26 +3,26 @@
|
||||
[quo2.components.record-audio.record-audio.style :as style]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn :refer [use-effect]]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.record-audio.record-audio.helpers :as helpers]))
|
||||
[quo2.components.record-audio.record-audio.helpers :refer [set-value]]))
|
||||
|
||||
(defn record-button
|
||||
[recording? reviewing-audio?]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [opacity (reanimated/use-shared-value 1)
|
||||
show-animation #(helpers/set-value opacity 1)
|
||||
hide-animation #(helpers/set-value opacity 0)]
|
||||
(let [opacity (ra/use-val 1)
|
||||
show-animation #(set-value opacity 1)
|
||||
hide-animation #(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)}
|
||||
[ra/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)}]]]))])
|
||||
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-100 colors/white)}]]]))])
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
[quo2.components.record-audio.record-audio.style :as style]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn :refer [use-effect]]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[react-native.audio-toolkit :as audio]
|
||||
[taoensso.timbre :as log]
|
||||
[cljs-bean.core :as bean]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.components.record-audio.record-audio.helpers :as helpers]
|
||||
[utils.worklets.record-audio :as worklets.record-audio]))
|
||||
[quo2.components.record-audio.record-audio.helpers :refer
|
||||
[animate-linear
|
||||
animate-linear-with-delay
|
||||
animate-linear-with-delay-loop
|
||||
animate-easing
|
||||
set-value]]))
|
||||
|
||||
(def ^:private scale-to-each 1.8)
|
||||
(def ^:private scale-to-total 2.6)
|
||||
@@ -19,13 +23,21 @@
|
||||
(def ^:private signal-anim-duration 3900)
|
||||
(def ^:private signal-anim-duration-2 1950)
|
||||
|
||||
(def ^:private record-audio-worklets (js/require "../src/js/record_audio_worklets.js"))
|
||||
|
||||
(defn- ring-scale
|
||||
[scale substract]
|
||||
(.ringScale ^js record-audio-worklets
|
||||
scale
|
||||
substract))
|
||||
|
||||
(def ^:private animated-ring
|
||||
(reagent/adapt-react-class
|
||||
(rn/memo
|
||||
(fn [props]
|
||||
(let [{:keys [scale opacity color]} (bean/bean props)]
|
||||
(reagent/as-element
|
||||
[reanimated/view {:style (style/animated-circle scale opacity color)}]))))))
|
||||
[ra/view {:style (style/animated-circle scale opacity color)}]))))))
|
||||
|
||||
(defn record-button-big
|
||||
[recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating?
|
||||
@@ -33,133 +45,129 @@
|
||||
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]
|
||||
[: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
|
||||
@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))]
|
||||
(let [scale (ra/use-val 1)
|
||||
opacity (ra/use-val 0)
|
||||
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
|
||||
animations (map
|
||||
(fn [index]
|
||||
(let [ring-scale (ring-scale scale (* scale-padding index))]
|
||||
{:scale ring-scale
|
||||
:opacity (ra/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 (ra/use-val 0)
|
||||
translate-x (ra/use-val 0)
|
||||
button-color colors/primary-50
|
||||
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white)
|
||||
icon-opacity (ra/use-val 1)
|
||||
red-overlay-opacity (ra/use-val 0)
|
||||
gray-overlay-opacity (ra/use-val 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 (audio/get-recorder-file-path @recorder-ref)))
|
||||
@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 []
|
||||
(set-value opacity 1)
|
||||
(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 []
|
||||
(set-value scale scale-to-each)
|
||||
(animate-linear-with-delay-loop scale
|
||||
scale-to-total
|
||||
signal-anim-duration-2
|
||||
0))
|
||||
signal-anim-duration)))
|
||||
stop-animation (fn []
|
||||
(set-value opacity 0)
|
||||
(ra/cancel-animation scale)
|
||||
(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)
|
||||
(animate-easing translate-y -64 250)
|
||||
(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 []
|
||||
(animate-easing translate-y 0 300)
|
||||
(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)
|
||||
(animate-easing translate-x -64 250)
|
||||
(animate-linear-with-delay icon-opacity 0 33.33 76.66)
|
||||
(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 []
|
||||
(animate-easing translate-x 0 300)
|
||||
(animate-linear icon-opacity 1 500)
|
||||
(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)
|
||||
(animate-easing translate-y -44 200)
|
||||
(animate-easing translate-x -44 200)
|
||||
(animate-linear-with-delay icon-opacity 0 33.33 33.33)
|
||||
(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 []
|
||||
(animate-easing translate-y 0 300)
|
||||
(animate-easing translate-x 0 300)
|
||||
(animate-linear icon-opacity 1 500)
|
||||
(animate-linear gray-overlay-opacity 0 800)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-at-initial-position? true))
|
||||
800))]
|
||||
(use-effect (fn []
|
||||
(cond
|
||||
@recording?
|
||||
@@ -182,7 +190,7 @@
|
||||
(start-x-animation)
|
||||
(reset-x-animation)))
|
||||
[@ready-to-delete?])
|
||||
[reanimated/view
|
||||
[ra/view
|
||||
{:style (style/record-button-big-container translate-x translate-y opacity)
|
||||
:pointer-events :none}
|
||||
[:<>
|
||||
@@ -195,9 +203,9 @@
|
||||
: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)}
|
||||
[ra/view {:style (style/record-button-big-red-overlay red-overlay-opacity)}]
|
||||
[ra/view {:style (style/record-button-big-gray-overlay gray-overlay-opacity)}]
|
||||
[ra/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}])]]]))])
|
||||
|
||||
@@ -2,65 +2,62 @@
|
||||
(:require [quo2.components.icon :as icons]
|
||||
[quo2.components.record-audio.record-audio.style :as style]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.reanimated :as ra]
|
||||
[react-native.core :refer [use-effect]]
|
||||
[quo2.components.record-audio.record-audio.helpers :as helpers]))
|
||||
[quo2.components.record-audio.record-audio.helpers :refer
|
||||
[animate-linear
|
||||
animate-linear-with-delay
|
||||
animate-easing-with-delay
|
||||
set-value]]))
|
||||
|
||||
(defn send-button
|
||||
[recording? ready-to-send? reviewing-audio? force-show-controls?]
|
||||
[recording? ready-to-send? reviewing-audio?]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
|
||||
translate-y (reanimated/use-shared-value (if force-show-controls? 76 20))
|
||||
connector-opacity (reanimated/use-shared-value 0)
|
||||
width (reanimated/use-shared-value 12)
|
||||
height (reanimated/use-shared-value 24)
|
||||
border-radius-first-half (reanimated/use-shared-value 16)
|
||||
border-radius-second-half (reanimated/use-shared-value 8)
|
||||
(let [opacity (ra/use-val 0)
|
||||
translate-y (ra/use-val 20)
|
||||
connector-opacity (ra/use-val 0)
|
||||
width (ra/use-val 12)
|
||||
height (ra/use-val 24)
|
||||
border-radius-first-half (ra/use-val 16)
|
||||
border-radius-second-half (ra/use-val 8)
|
||||
start-y-animation (fn []
|
||||
(helpers/animate-linear-with-delay translate-y 12 50 133.33)
|
||||
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
|
||||
(helpers/animate-easing-with-delay width 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay 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))
|
||||
(animate-linear-with-delay translate-y 12 50 133.33)
|
||||
(animate-easing-with-delay connector-opacity 1 0 93.33)
|
||||
(animate-easing-with-delay width 56 83.33 80)
|
||||
(animate-easing-with-delay height 56 83.33 80)
|
||||
(animate-easing-with-delay border-radius-first-half 28 83.33 80)
|
||||
(animate-easing-with-delay border-radius-second-half 28 83.33 80))
|
||||
reset-y-animation (fn []
|
||||
(helpers/animate-linear translate-y 0 100)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 12)
|
||||
(helpers/set-value height 24)
|
||||
(helpers/set-value border-radius-first-half 16)
|
||||
(helpers/set-value border-radius-second-half 8))
|
||||
(animate-linear translate-y 0 100)
|
||||
(set-value connector-opacity 0)
|
||||
(set-value width 12)
|
||||
(set-value height 24)
|
||||
(set-value border-radius-first-half 16)
|
||||
(set-value border-radius-second-half 8))
|
||||
fade-in-animation (fn []
|
||||
(helpers/animate-linear translate-y 0 200)
|
||||
(helpers/animate-linear opacity 1 200))
|
||||
(animate-linear translate-y 0 200)
|
||||
(animate-linear opacity 1 200))
|
||||
fade-out-animation (fn []
|
||||
(when-not force-show-controls?
|
||||
(helpers/animate-linear
|
||||
translate-y
|
||||
(if @reviewing-audio? 76 20)
|
||||
200))
|
||||
(animate-linear
|
||||
translate-y
|
||||
(if @reviewing-audio? 76 20)
|
||||
200)
|
||||
(when-not @reviewing-audio?
|
||||
(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))
|
||||
(animate-linear opacity 0 200))
|
||||
(set-value connector-opacity 0)
|
||||
(set-value width 24)
|
||||
(set-value height 12)
|
||||
(set-value border-radius-first-half 8)
|
||||
(set-value border-radius-second-half 16))
|
||||
fade-out-reset-animation (fn []
|
||||
(helpers/animate-linear opacity 0 200)
|
||||
(helpers/animate-linear-with-delay translate-y 20 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))]
|
||||
(animate-linear opacity 0 200)
|
||||
(animate-linear-with-delay translate-y 20 0 200)
|
||||
(set-value connector-opacity 0)
|
||||
(set-value width 24)
|
||||
(set-value height 12)
|
||||
(set-value border-radius-first-half 8)
|
||||
(set-value border-radius-second-half 16))]
|
||||
(use-effect (fn []
|
||||
(if @recording?
|
||||
(fade-in-animation)
|
||||
@@ -77,14 +74,14 @@
|
||||
@recording? (reset-y-animation)))
|
||||
[@ready-to-send?])
|
||||
[:<>
|
||||
[reanimated/view {:style (style/send-button-container opacity)}
|
||||
[reanimated/view
|
||||
[ra/view {:style (style/send-button-container opacity)}
|
||||
[ra/view
|
||||
{:style (style/send-button-connector connector-opacity
|
||||
width
|
||||
height
|
||||
border-radius-first-half
|
||||
border-radius-second-half)}]]
|
||||
[reanimated/view
|
||||
[ra/view
|
||||
{:style (style/send-button translate-y opacity)
|
||||
:pointer-events :none}
|
||||
[icons/icon :i/arrow-up
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(ns quo2.components.record-audio.record-audio.helpers
|
||||
(:require [react-native.reanimated :as reanimated]))
|
||||
(:require [react-native.reanimated :as ra]))
|
||||
|
||||
(defn animate-linear
|
||||
[shared-value value duration]
|
||||
(reanimated/animate-shared-value-with-timing
|
||||
(ra/animate
|
||||
shared-value
|
||||
value
|
||||
duration
|
||||
@@ -11,26 +11,26 @@
|
||||
|
||||
(defn animate-linear-with-delay
|
||||
[shared-value value duration delay]
|
||||
(reanimated/animate-shared-value-with-delay
|
||||
(ra/animate-delay
|
||||
shared-value
|
||||
value
|
||||
delay
|
||||
duration
|
||||
:linear
|
||||
delay))
|
||||
:linear))
|
||||
|
||||
(defn animate-linear-with-delay-loop
|
||||
[shared-value value duration delay]
|
||||
(reanimated/animate-shared-value-with-delay-repeat
|
||||
(ra/animate-delay-repeat
|
||||
shared-value
|
||||
value
|
||||
duration
|
||||
:linear
|
||||
delay
|
||||
-1))
|
||||
-1
|
||||
duration
|
||||
:linear))
|
||||
|
||||
(defn animate-easing
|
||||
[shared-value value duration]
|
||||
(reanimated/animate-shared-value-with-timing
|
||||
(ra/animate
|
||||
shared-value
|
||||
value
|
||||
duration
|
||||
@@ -38,13 +38,13 @@
|
||||
|
||||
(defn animate-easing-with-delay
|
||||
[shared-value value duration delay]
|
||||
(reanimated/animate-shared-value-with-delay
|
||||
(ra/animate-delay
|
||||
shared-value
|
||||
value
|
||||
delay
|
||||
duration
|
||||
:easing1
|
||||
delay))
|
||||
:easing1))
|
||||
|
||||
(defn set-value
|
||||
[shared-value value]
|
||||
(reanimated/set-shared-value shared-value value))
|
||||
(ra/set-val shared-value value))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
(ns quo2.components.record-audio.record-audio.style
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
[react-native.reanimated :as ra]))
|
||||
|
||||
(defn animated-circle
|
||||
[scale opacity color]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:transform [{:scale scale}]
|
||||
:opacity opacity}
|
||||
{:width 56
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
(defn record-button-big-container
|
||||
[translate-x translate-y opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:transform [{:translateY translate-y}
|
||||
{:translateX translate-x}]
|
||||
:opacity opacity}
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
(defn record-button-big-red-overlay
|
||||
[red-overlay-opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity red-overlay-opacity}
|
||||
{:position :absolute
|
||||
:top 0
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
(defn record-button-big-gray-overlay
|
||||
[gray-overlay-opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity gray-overlay-opacity}
|
||||
{:position :absolute
|
||||
:top 0
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
(defn record-button-big-icon-container
|
||||
[icon-opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity icon-opacity}
|
||||
{}))
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
(defn send-button-container
|
||||
[opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity opacity}
|
||||
{:justify-content :center
|
||||
:align-items :center
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
(defn send-button-connector
|
||||
[opacity width height border-radius-first-half border-radius-second-half]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity opacity
|
||||
:width width
|
||||
:height height
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
(defn send-button
|
||||
[translate-y opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:transform [{:translateY translate-y}]
|
||||
:opacity opacity}
|
||||
{:justify-content :center
|
||||
@@ -125,7 +125,7 @@
|
||||
|
||||
(defn lock-button-container
|
||||
[opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity opacity}
|
||||
{:transform [{:rotate "45deg"}]
|
||||
:justify-content :center
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
(defn lock-button-connector
|
||||
[opacity width height border-radius-first-half border-radius-second-half]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity opacity
|
||||
:width width
|
||||
:height height
|
||||
@@ -154,7 +154,7 @@
|
||||
|
||||
(defn lock-button
|
||||
[translate-x-y opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:transform [{:translateX translate-x-y}
|
||||
{:translateY translate-x-y}]
|
||||
:opacity opacity}
|
||||
@@ -172,7 +172,7 @@
|
||||
|
||||
(defn delete-button-container
|
||||
[opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity opacity}
|
||||
{:justify-content :center
|
||||
:align-items :center
|
||||
@@ -184,7 +184,7 @@
|
||||
|
||||
(defn delete-button-connector
|
||||
[opacity width height border-radius-first-half border-radius-second-half]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity opacity
|
||||
:width width
|
||||
:height height
|
||||
@@ -200,7 +200,7 @@
|
||||
|
||||
(defn delete-button
|
||||
[scale translate-x opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:transform [{:translateX translate-x}
|
||||
{:scale scale}]
|
||||
:opacity opacity}
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
(defn record-button-container
|
||||
[opacity]
|
||||
(reanimated/apply-animations-to-style
|
||||
(ra/apply-animations-to-style
|
||||
{:opacity opacity}
|
||||
{:margin-bottom 32
|
||||
:margin-right 32}))
|
||||
|
||||
@@ -14,17 +14,19 @@
|
||||
[quo2.components.record-audio.record-audio.buttons.send-button :as send-button]
|
||||
[quo2.components.record-audio.record-audio.buttons.lock-button :as lock-button]
|
||||
[quo2.components.record-audio.record-audio.buttons.delete-button :as delete-button]
|
||||
[quo2.components.record-audio.record-audio.buttons.record-button :as record-button]
|
||||
[react-native.platform :as platform]
|
||||
[clojure.string :as string]))
|
||||
[quo2.components.record-audio.record-audio.buttons.record-button :as record-button]))
|
||||
|
||||
(def ^:private min-audio-duration-ms 500)
|
||||
(def ^:private max-audio-duration-ms 120000)
|
||||
(def ^:private metering-interval 100)
|
||||
(def ^:private base-filename "am")
|
||||
(def ^:private default-format ".aac")
|
||||
(def ^:private base-filename "am.")
|
||||
(def ^:private default-format "aac")
|
||||
|
||||
(def min-touch-duration 150)
|
||||
(def ^:private rec-options
|
||||
(merge
|
||||
audio/default-recorder-options
|
||||
{:filename (str base-filename default-format)
|
||||
:meteringInterval metering-interval}))
|
||||
|
||||
(def ^:private record-button-area-big
|
||||
{:width 56
|
||||
@@ -147,10 +149,8 @@
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]))])
|
||||
|
||||
(defn view
|
||||
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio
|
||||
record-audio-permission-granted
|
||||
on-request-record-audio-permission on-check-audio-permissions
|
||||
audio-file]}]
|
||||
[{:keys [on-start-recording on-send on-cancel on-reviewing-audio record-audio-permission-granted
|
||||
on-request-record-audio-permission on-check-audio-permissions]}]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [recording? (reagent/atom false)
|
||||
@@ -163,39 +163,30 @@
|
||||
recording-length-ms (reagent/atom 0)
|
||||
audio-current-time-ms (reagent/atom 0)
|
||||
seeking-audio? (reagent/atom false)
|
||||
force-show-controls? (reagent/atom false)
|
||||
clear-timeout (atom nil)
|
||||
record-button-at-initial-position? (atom true)
|
||||
record-button-is-animating? (atom false)
|
||||
idle? (atom false)
|
||||
recorder-ref (atom nil)
|
||||
player-ref (atom nil)
|
||||
touch-active? (atom false)
|
||||
recording-timer (atom nil)
|
||||
playing-timer (atom nil)
|
||||
recorder-ref (atom nil)
|
||||
player-ref (atom nil)
|
||||
output-file (atom nil)
|
||||
reached-max-duration? (atom false)
|
||||
touch-timestamp (atom nil)
|
||||
disabled? (atom false)
|
||||
rec-options
|
||||
(merge
|
||||
audio/default-recorder-options
|
||||
{:filename (str base-filename (.now js/Date) default-format)
|
||||
:meteringInterval metering-interval})
|
||||
destroy-player
|
||||
(fn []
|
||||
(audio/destroy-player @player-ref)
|
||||
(reset! player-ref nil))
|
||||
reload-player
|
||||
(fn [audio-file]
|
||||
(fn []
|
||||
(when @player-ref
|
||||
(destroy-player))
|
||||
(reset! player-ref
|
||||
(audio/new-player
|
||||
(or audio-file (:filename rec-options))
|
||||
(:filename rec-options)
|
||||
{:autoDestroy false
|
||||
:continuesToPlayInBackground false
|
||||
:category audio/PLAYBACK}
|
||||
:continuesToPlayInBackground false}
|
||||
(fn []
|
||||
(reset! playing-audio? false)
|
||||
(when @playing-timer
|
||||
@@ -219,37 +210,9 @@
|
||||
rec-options
|
||||
#(log/debug "[record-audio] new recorder - on meter")
|
||||
#(log/debug "[record-audio] new recorder - on ended"))))
|
||||
reset-recorder
|
||||
(fn []
|
||||
(reset! recording? false)
|
||||
(reset! reviewing-audio? false)
|
||||
(reset! locked? false)
|
||||
(reset! ready-to-send? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(reset! ready-to-delete? false)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! recording-length-ms 0)
|
||||
(reset! seeking-audio? false)
|
||||
(reset! playing-audio? false)
|
||||
(reset! touch-active? false)
|
||||
(reset! reached-max-duration? false)
|
||||
(reset! output-file nil)
|
||||
(reset! idle? false)
|
||||
(reset! record-button-is-animating? false)
|
||||
(reset! record-button-at-initial-position? true)
|
||||
(when @clear-timeout
|
||||
(js/clearTimeout @clear-timeout)
|
||||
(reset! clear-timeout nil))
|
||||
(when @recording-timer
|
||||
(js/clearInterval @recording-timer)
|
||||
(reset! recording-timer nil))
|
||||
(when @playing-timer
|
||||
(js/clearInterval @playing-timer)
|
||||
(reset! playing-timer nil))
|
||||
(reload-recorder))
|
||||
on-start-should-set-responder
|
||||
(fn [^js e]
|
||||
(when-not (or @locked? @idle? (nil? e) @disabled?)
|
||||
(when-not (or @locked? @idle? (nil? e))
|
||||
(let [pressed-record-button? (touch-inside-area?
|
||||
{:location-x (oops/oget e "nativeEvent.locationX")
|
||||
:location-y (oops/oget e "nativeEvent.locationY")
|
||||
@@ -257,12 +220,7 @@
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
record-button-area)
|
||||
new-recorder (audio/new-recorder
|
||||
rec-options
|
||||
#(log/debug "[record-audio] new recorder - on meter")
|
||||
#(log/debug "[record-audio] new recorder - on ended"))]
|
||||
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
|
||||
record-button-area)]
|
||||
(when-not @reviewing-audio?
|
||||
(if record-audio-permission-granted
|
||||
(do
|
||||
@@ -273,9 +231,8 @@
|
||||
(when @recording-timer
|
||||
(js/clearInterval @recording-timer))
|
||||
(reset! output-file nil)
|
||||
(reset! recorder-ref new-recorder)
|
||||
(audio/start-recording
|
||||
new-recorder
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! recording-timer
|
||||
@@ -294,29 +251,28 @@
|
||||
(reset! ready-to-send? false)
|
||||
(reset! ready-to-delete? false)
|
||||
(audio/stop-recording
|
||||
new-recorder
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(reset! output-file (audio/get-recorder-file-path
|
||||
new-recorder))
|
||||
(reload-player nil)
|
||||
@recorder-ref))
|
||||
(reload-recorder)
|
||||
(reload-player)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(js/clearInterval @recording-timer)
|
||||
(reset! recording-length-ms 0)
|
||||
(when on-reviewing-audio
|
||||
(on-reviewing-audio (audio/get-recorder-file-path
|
||||
new-recorder))))))
|
||||
(on-reviewing-audio)))))
|
||||
metering-interval))
|
||||
(log/debug "[record-audio] start recording - success"))
|
||||
#(log/error "[record-audio] start recording - error: " %))
|
||||
(when on-start-recording
|
||||
(on-start-recording))))
|
||||
(when on-request-record-audio-permission
|
||||
(on-request-record-audio-permission))))
|
||||
(some-> on-request-record-audio-permission)))
|
||||
(when record-audio-permission-granted
|
||||
(reset! touch-active? true))))
|
||||
(and (not @idle?) (not @disabled?)))
|
||||
(not @idle?))
|
||||
on-responder-move
|
||||
(fn [^js e]
|
||||
(when-not @locked?
|
||||
@@ -393,49 +349,43 @@
|
||||
(when (and
|
||||
(not @idle?)
|
||||
(not @reached-max-duration?))
|
||||
(let [touch-area {:location-x (oops/oget e "nativeEvent.locationX")
|
||||
:location-y (oops/oget e "nativeEvent.locationY")
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
on-record-button? (touch-inside-area?
|
||||
touch-area
|
||||
record-button-area-big)
|
||||
on-send-button? (touch-inside-area?
|
||||
touch-area
|
||||
(send-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))
|
||||
on-delete-button? (touch-inside-area?
|
||||
touch-area
|
||||
(delete-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))
|
||||
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
|
||||
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
|
||||
audio-length @recording-length-ms]
|
||||
(let [touch-area {:location-x (oops/oget e "nativeEvent.locationX")
|
||||
:location-y (oops/oget e "nativeEvent.locationY")
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
on-record-button? (touch-inside-area?
|
||||
touch-area
|
||||
record-button-area-big)
|
||||
on-send-button? (touch-inside-area?
|
||||
touch-area
|
||||
(send-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))
|
||||
on-delete-button? (touch-inside-area?
|
||||
touch-area
|
||||
(delete-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))]
|
||||
(cond
|
||||
(and @reviewing-audio? on-send-button?)
|
||||
(do
|
||||
(reset! reviewing-audio? false)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! force-show-controls? false)
|
||||
(when on-send
|
||||
(on-send {:file-path @output-file
|
||||
:duration (int (audio/get-player-duration @player-ref))}))
|
||||
(when @player-ref
|
||||
(audio/stop-playing
|
||||
@player-ref
|
||||
(fn []
|
||||
(destroy-player)
|
||||
(log/debug "[record-audio] stop playing - success"))
|
||||
#(log/error "[record-audio] stop playing - error: " %))))
|
||||
#(log/error "[record-audio] stop playing - error: " %)))
|
||||
(when on-send
|
||||
(on-send @output-file)))
|
||||
(and @reviewing-audio? on-delete-button?)
|
||||
(do
|
||||
(reset! reviewing-audio? false)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! force-show-controls? false)
|
||||
(destroy-player)
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
@@ -443,86 +393,61 @@
|
||||
(do
|
||||
(reset! locked? true)
|
||||
(reset! ready-to-lock? false))
|
||||
(and (not @reviewing-audio?)
|
||||
(or on-record-button?
|
||||
(and (not @ready-to-delete?)
|
||||
(not @ready-to-lock?)
|
||||
(not @ready-to-send?))))
|
||||
(and (not @reviewing-audio?) on-record-button?)
|
||||
(do
|
||||
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
|
||||
(js/setTimeout
|
||||
(if (>= @recording-length-ms min-audio-duration-ms)
|
||||
(do (reset! reviewing-audio? true)
|
||||
(reset! idle? false)
|
||||
(when on-reviewing-audio
|
||||
(on-reviewing-audio)))
|
||||
(do (when on-cancel
|
||||
(on-cancel))
|
||||
(reset! idle? true)))
|
||||
(reset! locked? false)
|
||||
(reset! recording? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(if (>= @recording-length-ms min-audio-duration-ms)
|
||||
(do (reset! reviewing-audio? true)
|
||||
(reset! idle? false)
|
||||
(when on-reviewing-audio
|
||||
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
|
||||
(do (when on-cancel
|
||||
(on-cancel))
|
||||
(reset! idle? true)))
|
||||
(reset! locked? false)
|
||||
(reset! recording? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
|
||||
(when (>= audio-length min-audio-duration-ms)
|
||||
(reload-player nil))
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(js/clearInterval @recording-timer)
|
||||
(reset! recording-length-ms 0)
|
||||
(reset! disabled? false))
|
||||
(if (> touch-timestamp-diff min-touch-duration) 0 250)))
|
||||
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
|
||||
(reload-recorder)
|
||||
(reload-player)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(js/clearInterval @recording-timer)
|
||||
(reset! recording-length-ms 0))
|
||||
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
|
||||
(do
|
||||
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(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)))
|
||||
(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)
|
||||
(reset! disabled? false)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %)))
|
||||
(if (> touch-timestamp-diff min-touch-duration) 0 250)))))
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(cond
|
||||
@ready-to-send?
|
||||
(when on-send
|
||||
(on-send (audio/get-recorder-file-path @recorder-ref)))
|
||||
@ready-to-delete?
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
(reload-recorder)
|
||||
(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: " %))))
|
||||
(reset! touch-active? false))
|
||||
(when @reached-max-duration?
|
||||
(reset! reached-max-duration? false))
|
||||
(reset! touch-timestamp nil))]
|
||||
(reset! reached-max-duration? false)))]
|
||||
(fn []
|
||||
(use-effect (fn []
|
||||
(when on-check-audio-permissions
|
||||
(on-check-audio-permissions))
|
||||
(when on-init
|
||||
(on-init reset-recorder))
|
||||
(when audio-file
|
||||
(let [filename (last (string/split audio-file "/"))]
|
||||
(reload-player filename)
|
||||
(reset! output-file audio-file)
|
||||
(reset! reviewing-audio? true)
|
||||
(reset! force-show-controls? true)))))
|
||||
(some-> on-check-audio-permissions)
|
||||
(reload-recorder)))
|
||||
[rn/view
|
||||
{:style style/bar-container
|
||||
:pointer-events :box-none}
|
||||
{:style style/bar-container}
|
||||
(when @reviewing-audio?
|
||||
[:<>
|
||||
[play-button playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio?]
|
||||
@@ -538,18 +463,13 @@
|
||||
[rn/view
|
||||
{:test-ID "record-audio"
|
||||
:style style/button-container
|
||||
:hit-slop {:top (if platform/ios? -70 0)
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0}
|
||||
:pointer-events :box-only
|
||||
:on-start-should-set-responder on-start-should-set-responder
|
||||
:on-responder-move on-responder-move
|
||||
:on-responder-release on-responder-release}
|
||||
[delete-button/delete-button recording? ready-to-delete? reviewing-audio?
|
||||
@force-show-controls?]
|
||||
[delete-button/delete-button recording? ready-to-delete? reviewing-audio?]
|
||||
[lock-button/lock-button recording? ready-to-lock? locked?]
|
||||
[send-button/send-button recording? ready-to-send? reviewing-audio? @force-show-controls?]
|
||||
[send-button/send-button recording? ready-to-send? reviewing-audio?]
|
||||
[record-button-big/record-button-big
|
||||
recording?
|
||||
ready-to-send?
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn card-background
|
||||
[{:keys [customization-color]}]
|
||||
[{:keys [custom-color]}]
|
||||
[:<>
|
||||
[rn/view {:style (style/background-top customization-color)}]
|
||||
[rn/view {:style (style/background-top custom-color)}]
|
||||
[rn/view {:style (style/background-bottom)}]])
|
||||
|
||||
(defn avatar
|
||||
@@ -28,12 +28,12 @@
|
||||
:i/more]])
|
||||
|
||||
(defn account
|
||||
[{:keys [account-name account-address avatar-icon customization-color on-press-menu]}]
|
||||
[{:keys [account-name account-address avatar-icon custom-color on-press-menu]}]
|
||||
[rn/view {:style style/card}
|
||||
[card-background {:customization-color customization-color}]
|
||||
[card-background {:custom-color custom-color}]
|
||||
[rn/view {:style style/card-top}
|
||||
[avatar
|
||||
{:color customization-color
|
||||
{:color custom-color
|
||||
:icon avatar-icon}]
|
||||
[menu-button {:on-press on-press-menu}]]
|
||||
[rn/view {:style style/card-bottom}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
(ns quo2.components.tags.--tests--.status-tags-component-spec
|
||||
(:require [quo2.components.tags.status-tags :as quo2]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(defn render-status-tag
|
||||
[opts]
|
||||
(h/render [quo2/status-tag opts]))
|
||||
|
||||
(h/describe "status tag component"
|
||||
(h/test "renders status tag with positive type"
|
||||
(render-status-tag {:status {:type :positive}
|
||||
:label "Positive"
|
||||
:size :small})
|
||||
(-> (h/expect (h/get-all-by-label-text :status-tag-positive))
|
||||
(.toBeTruthy))
|
||||
(-> (h/expect (h/get-by-text "Positive"))
|
||||
(.toBeTruthy)))
|
||||
(h/test "renders status tag with negative type"
|
||||
(render-status-tag {:status {:type :negative}
|
||||
:label "Negative"
|
||||
:size :small})
|
||||
(-> (h/expect (h/get-all-by-label-text :status-tag-negative))
|
||||
(.toBeTruthy))
|
||||
(-> (h/expect (h/get-by-text "Negative"))
|
||||
(.toBeTruthy)))
|
||||
(h/test "renders status tag with pending type"
|
||||
(render-status-tag {:status {:type :pending}
|
||||
:label "Pending"
|
||||
:size :small})
|
||||
(-> (h/expect (h/get-all-by-label-text :status-tag-pending))
|
||||
(.toBeTruthy))
|
||||
(-> (h/expect (h/get-by-text "Pending"))
|
||||
(.toBeTruthy))))
|
||||
@@ -27,18 +27,15 @@
|
||||
background-color
|
||||
icon
|
||||
text-color
|
||||
label
|
||||
accessibility-label]}]
|
||||
label]}]
|
||||
(let [paragraph-size (if (= size :small) :paragraph-2 :paragraph-1)]
|
||||
[rn/view
|
||||
{:accessible true
|
||||
:accessibility-label accessibility-label
|
||||
:style (assoc (if (= size :small)
|
||||
small-container-style
|
||||
large-container-style)
|
||||
:border-width 1
|
||||
:border-color border-color
|
||||
:background-color background-color)}
|
||||
(assoc (if (= size :small)
|
||||
small-container-style
|
||||
large-container-style)
|
||||
:border-width 1
|
||||
:border-color border-color
|
||||
:background-color background-color)
|
||||
[rn/view
|
||||
{:flex-direction :row
|
||||
:flex 1}
|
||||
@@ -48,7 +45,7 @@
|
||||
[icon/icon
|
||||
icon
|
||||
{:no-color true
|
||||
:size (if (= size :large) 20 12)}]]
|
||||
:size 12}]]
|
||||
[text/text
|
||||
{:size paragraph-size
|
||||
:weight :medium
|
||||
@@ -56,51 +53,41 @@
|
||||
:color text-color}} label]]])))
|
||||
|
||||
(defn- positive
|
||||
[size theme label _]
|
||||
[size theme label]
|
||||
[base-tag
|
||||
{:accessibility-label :status-tag-positive
|
||||
:size size
|
||||
:icon :i/positive-state
|
||||
:background-color colors/success-50-opa-10
|
||||
:border-color colors/success-50-opa-20
|
||||
:label label
|
||||
;; The positive tag uses the same color for `light` and `dark blur` variant
|
||||
:text-color (if (= theme :dark) colors/success-60 colors/success-50)}])
|
||||
{:size size
|
||||
:icon :verified
|
||||
:background-color colors/success-50-opa-10
|
||||
:border-color colors/success-50-opa-20
|
||||
:label label
|
||||
:text-color (if (= theme :light)
|
||||
colors/success-50
|
||||
colors/success-60)}])
|
||||
|
||||
(defn- negative
|
||||
[size theme label _]
|
||||
[size theme label]
|
||||
[base-tag
|
||||
{:accessibility-label :status-tag-negative
|
||||
:size size
|
||||
:icon :i/negative-state
|
||||
:background-color colors/danger-50-opa-10
|
||||
:border-color colors/danger-50-opa-20
|
||||
:label label
|
||||
;; The negative tag uses the same color for `dark` and `dark blur` variant
|
||||
:text-color (if (= theme :light) colors/danger-50 colors/danger-60)}])
|
||||
{:size size
|
||||
:icon :untrustworthy
|
||||
:background-color colors/danger-50-opa-10
|
||||
:border-color colors/danger-50-opa-20
|
||||
:label label
|
||||
:text-color (if (= theme :light)
|
||||
colors/danger-50
|
||||
colors/danger-60)}])
|
||||
|
||||
(defn- pending
|
||||
[size theme label blur?]
|
||||
[size _ label]
|
||||
[base-tag
|
||||
{:accessibility-label :status-tag-pending
|
||||
:size size
|
||||
:label label
|
||||
:icon (if blur?
|
||||
:i/pending-dark-blur
|
||||
(if (= theme :light) :i/pending-light :i/pending-dark))
|
||||
:background-color (if blur?
|
||||
colors/white-opa-5
|
||||
(colors/theme-colors colors/neutral-10 colors/neutral-80-opa-40 theme))
|
||||
:border-color (if blur?
|
||||
colors/white-opa-5
|
||||
(colors/theme-colors colors/neutral-20 colors/neutral-80 theme))
|
||||
|
||||
:text-color (if blur?
|
||||
colors/white-opa-70
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}])
|
||||
{:size size
|
||||
:icon :pending
|
||||
:label label
|
||||
:background-color colors/white-opa-5
|
||||
:border-color colors/white-opa-5
|
||||
:text-color colors/white-opa-70}])
|
||||
|
||||
(defn status-tag
|
||||
[{:keys [status size override-theme label blur?]}]
|
||||
[{:keys [status size override-theme label]}]
|
||||
(when status
|
||||
(when-let [status-component (case (:type status)
|
||||
:positive positive
|
||||
@@ -110,5 +97,4 @@
|
||||
[status-component
|
||||
size
|
||||
(or override-theme (quo2.theme/get-theme))
|
||||
label
|
||||
blur?])))
|
||||
label])))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
quo2.components.avatars.channel-avatar
|
||||
quo2.components.avatars.group-avatar
|
||||
quo2.components.avatars.icon-avatar
|
||||
quo2.components.avatars.user-avatar.view
|
||||
quo2.components.avatars.user-avatar
|
||||
quo2.components.avatars.wallet-user-avatar
|
||||
quo2.components.banners.banner.view
|
||||
quo2.components.buttons.button
|
||||
@@ -30,9 +30,7 @@
|
||||
quo2.components.icon
|
||||
quo2.components.info.info-message
|
||||
quo2.components.info.information-box
|
||||
quo2.components.inputs.input.view
|
||||
quo2.components.inputs.title-input.view
|
||||
quo2.components.inputs.profile-input.view
|
||||
quo2.components.input.view
|
||||
quo2.components.list-items.channel
|
||||
quo2.components.list-items.menu-item
|
||||
quo2.components.list-items.preview-list
|
||||
@@ -52,7 +50,6 @@
|
||||
quo2.components.profile.profile-card.view
|
||||
quo2.components.profile.select-profile.view
|
||||
quo2.components.reactions.reaction
|
||||
quo2.components.record-audio.record-audio.view
|
||||
quo2.components.selectors.disclaimer.view
|
||||
quo2.components.selectors.filter.view
|
||||
quo2.components.selectors.selectors
|
||||
@@ -105,7 +102,7 @@
|
||||
(def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar)
|
||||
(def group-avatar quo2.components.avatars.group-avatar/group-avatar)
|
||||
(def icon-avatar quo2.components.avatars.icon-avatar/icon-avatar)
|
||||
(def user-avatar quo2.components.avatars.user-avatar.view/user-avatar)
|
||||
(def user-avatar quo2.components.avatars.user-avatar/user-avatar)
|
||||
(def wallet-user-avatar quo2.components.avatars.wallet-user-avatar/wallet-user-avatar)
|
||||
|
||||
;;;; BANNER
|
||||
@@ -144,9 +141,7 @@
|
||||
(def permission-context quo2.components.drawers.permission-context.view/view)
|
||||
|
||||
;;;; INPUTS
|
||||
(def input quo2.components.inputs.input.view/input)
|
||||
(def profile-input quo2.components.inputs.profile-input.view/profile-input)
|
||||
(def title-input quo2.components.inputs.title-input.view/title-input)
|
||||
(def input quo2.components.input.view/input)
|
||||
|
||||
;;;; LIST ITEMS
|
||||
(def channel-list-item quo2.components.list-items.channel/list-item)
|
||||
@@ -167,9 +162,6 @@
|
||||
(def profile-card quo2.components.profile.profile-card.view/profile-card)
|
||||
(def select-profile quo2.components.profile.select-profile.view/view)
|
||||
|
||||
;;;; RECORD AUDIO
|
||||
(def record-audio quo2.components.record-audio.record-audio.view/record-audio)
|
||||
|
||||
;;;; SETTINGS
|
||||
(def privacy-option quo2.components.settings.privacy-option/card)
|
||||
(def account quo2.components.settings.accounts.view/account)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
(ns quo2.core-spec
|
||||
(:require [quo2.components.avatars.user-avatar.component-spec]
|
||||
[quo2.components.banners.banner.component-spec]
|
||||
(:require [quo2.components.banners.banner.component-spec]
|
||||
[quo2.components.buttons.--tests--.buttons-component-spec]
|
||||
[quo2.components.counter.--tests--.counter-component-spec]
|
||||
[quo2.components.dividers.--tests--.divider-label-component-spec]
|
||||
@@ -9,8 +8,6 @@
|
||||
[quo2.components.drawers.drawer-buttons.component-spec]
|
||||
[quo2.components.drawers.permission-context.component-spec]
|
||||
[quo2.components.colors.color-picker.component-spec]
|
||||
[quo2.components.inputs.profile-input.component-spec]
|
||||
[quo2.components.inputs.title-input.component-spec]
|
||||
[quo2.components.markdown.--tests--.text-component-spec]
|
||||
[quo2.components.onboarding.small-option-card.component-spec]
|
||||
[quo2.components.password.tips.component-spec]
|
||||
@@ -18,5 +15,4 @@
|
||||
[quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec]
|
||||
[quo2.components.profile.select-profile.component-spec]
|
||||
[quo2.components.selectors.--tests--.selectors-component-spec]
|
||||
[quo2.components.selectors.filter.component-spec]
|
||||
[quo2.components.tags.--tests--.status-tags-component-spec]))
|
||||
[quo2.components.selectors.filter.component-spec]))
|
||||
|
||||
@@ -72,9 +72,6 @@
|
||||
(def neutral-80-opa-90 (alpha neutral-80 0.9))
|
||||
(def neutral-80-opa-95 (alpha neutral-80 0.95))
|
||||
|
||||
;;90 with transparency
|
||||
(def neutral-90-opa-0 (alpha neutral-90 0))
|
||||
|
||||
;;95 with transparency
|
||||
(def neutral-95-opa-60 (alpha neutral-95 0.6))
|
||||
(def neutral-95-opa-70 (alpha neutral-95 0.7))
|
||||
@@ -101,7 +98,6 @@
|
||||
(def white "#ffffff")
|
||||
|
||||
;; with transparency
|
||||
(def white-opa-0 (alpha white 0))
|
||||
(def white-opa-5 (alpha white 0.05))
|
||||
(def white-opa-10 (alpha white 0.1))
|
||||
(def white-opa-20 (alpha white 0.2))
|
||||
@@ -143,8 +139,8 @@
|
||||
;;;;Success
|
||||
|
||||
;;Solid
|
||||
(def success-50 "#23ADA0")
|
||||
(def success-60 "#1C8A80")
|
||||
(def success-50 "#26A69A")
|
||||
(def success-60 "#208B81")
|
||||
|
||||
;;50 with transparency
|
||||
(def success-50-opa-5 (alpha success-50 0.05))
|
||||
@@ -166,8 +162,8 @@
|
||||
(def danger-opa-40 (alpha danger 0.4))
|
||||
|
||||
;;Solid
|
||||
(def danger-50 "#E95460")
|
||||
(def danger-60 "#BA434D")
|
||||
(def danger-50 "#E65F5C")
|
||||
(def danger-60 "#C1504D")
|
||||
|
||||
;;50 with transparency
|
||||
(def danger-50-opa-5 (alpha danger-50 0.05))
|
||||
|
||||