Compare commits

...
Author SHA1 Message Date
Omar Basem db6e36961f updates 2023-03-16 13:54:29 +04:00
Omar Basem 51843e0b41 updates 2023-03-16 11:35:06 +04:00
Omar Basem bc3e3d30bd updates 2023-03-16 10:11:57 +04:00
Omar Basem 9f9124aa0b updates 2023-03-16 09:51:43 +04:00
Omar Basem 33674861d0 updates 2023-03-16 09:39:07 +04:00
Omar Basem 32d8a52acf updates 2023-03-16 09:23:18 +04:00
Omar Basem d3635b7c91 updates 2023-03-16 09:22:32 +04:00
yqrashawn 9723479634 refactor: chat message list context related (#15313) 2023-03-16 10:52:47 +08:00
Alexander dabe8285be Bugfixes for the style of "New to Status" screen (#15353)
* Bugfixes for the style of "New to Status" screen

* Updates
2023-03-15 20:02:26 +01:00
Ibrahem Khalil f981021a1f Code Improvements of 15048 (#15351) 2023-03-15 20:08:59 +02:00
Churikova Tetiana b5781a7a83 e2e: minor fixes 2023-03-15 16:49:37 +01:00
Icaro Motta e8556a9abf Show AC unread indicator with counter and seen state color (#15304)
- Display Activity Center unread badge with the unread counter.
- Use the new seen state stored in `status-go` to change the color of the
  notification.
- Performance: split the `top-nav` component into left and right section
  components and render the unread indicator in a separate component to not
  trigger the re-render of the entire `top-nav` (as was before).
  
Fixes https://github.com/status-im/status-mobile/issues/14851

Demo: https://user-images.githubusercontent.com/46027/224299978-770dd5f1-302b-4375-af2b-3cd181ffdc9d.webm

Notes
=====

- Fix/improve: `quo/counter` displayed `NaN` to the user if the input value was
  an empty string.
- In Figma, there's a border around the unread indicator. I didn't implement
  this because the ideal solution IMO involves changing the `quo/counter`
  component a little bit because the width of the component varies according to
  the content displayed (1, 9, 99, 100, etc) and I wanted to the right thing in
  a separate PR.

Design notes
============

There's an ongoing conversation with the Design team to decide what to do with
the gray indicator on top of the bell icon, since there's little contrast when
it's is in the `seen` state.

Platforms
=========

- Android
- iOS

Steps to test
=============

- Open Status
- Receive one or more notifications in the Home screen and check the unread
  indicator is blue and has a counter.
- Open the AC and close it, notice the unread indicator is now in the `seen`
  state. You can close the app and re-open and the state is persisted.
- Mark notifications as read/unread at will, check the unread counter is
  correct.
2023-03-15 12:41:34 -03:00
Omar Basem f640eb8c8f feat: drag to dismiss lightbox (#15349)
* feat: drag to dismiss lightbox
2023-03-15 16:37:01 +04:00
Alexander 8546727f84 Spacing between codeblocks and regular text + fixes for "edited at" message (#15307)
* Spacing between codeblocks and regular text + fixes for "edited at" message

Fixes

Post-review updates

* Lint fixes
2023-03-15 11:53:20 +01:00
Jamie Caprani de6a736c10 feat: add ability to cancel a request to join a community (#14973)
https://github.com/status-im/status-go/compare/80d350ad...5d818669
2023-03-14 16:14:37 -07:00
Roman Volosovskyi 61600a534b [#15152] SHow ENS name on adding contact via ENS name 2023-03-14 19:34:39 +01:00
Ibrahem Khalil 181e5c2266 Flexsurfers' comments on status-tags (#15345) 2023-03-14 18:46:35 +01:00
Icaro Motta 9473d3f40c Swipe gestures for Activity Center notifications with CTA (#15284)
Implements swipe actions for notifications with call to action (e.g. pending
contact requests, unverified identity verifications, etc).

Fixes https://github.com/status-im/status-mobile/issues/15118

According to the Design team, the goal is to deliver a consistent experience to
users, so whenever the user sees a notification with buttons, the same actions
can be taken via the swipe buttons.

Note: swipe buttons are using placeholder icons while the Design team works out
which ones to use

Additionally, a bunch of fixes:

- Fix: outgoing pending contact requests were not being removed from the UI when
  cancelled.
- Fix: Membership tab not showing unread indicator.
- Fix: dismissed membership notification not marked as read.
- Fix: dismissed membership notification was displaying decline/accept buttons.
  Regression came from changes in status-go related to soft deletion of
  notifications.
- Fix: incorrect check for the pending state of a contact request.
- Fixed lots of bugs for identity verification notifications, as it was
  completely broken. Unfortunately, somebody made lots of changes without
  actually testing the flows.
- Add basic error handling and log if accepting, declining or canceling contact
  requests fail.

The demo shows an identity verification with swipe actions to reply or decline.
[identity-verification-swipe-to-reply.webm](https://user-images.githubusercontent.com/46027/223565755-b2ca3f68-12e2-4e1e-9e52-edd52cfcc971.webm)

Out of scope: The old quo input is still in use in the identity verification
notification. This will eventually be solved by issue
https://github.com/status-im/status-mobile/issues/14364

### Steps to test

Notifications with one or more buttons (actions) are affected by this change,
because now the user can also swipe left/right to act on them.

- Membership notifications: private group chat. The following PR explains how to
  generate them https://github.com/status-im/status-mobile/pull/14785
- Contact requests, and community gated requests to join (Admin tab).
- Identity verifications. I believe the only way to test identity verification
  flows at the moment is to use the Desktop app, since initiating the challenge
  is not implemented in Mobile yet.
- Mentions and replies don't have new swipe buttons because they don't have call
  to action buttons throughout their lifecycle.

Steps to test identity verification flows:

#### Identity verification flow 1

- `A` and `B` are mutual contacts.
- `A` sends a verification request to `B`.
- `A` should not see any notification yet.
- `B` should receive an identity verification notification. `B` can either
  decline or reply.
- `B` declines and the status `Declined` is shown instead of buttons.
- `B` can now either swipe to toggle read/unread or swipe delete the
  notification.
- `A` should not receive any notification after `A` declined.

#### Identity verification flow 2

- `A` and `B` are mutual contacts.
- `A` sends a verification request to `B`.
- `A` should not see any notification yet.
- `B` should receive an identity verification notification. `B` can either
  decline or reply.
- `B` press `Reply` and a bottom sheet is displayed with a text input.
- `B` sends the reply/answer message and the status `Replied` is shown instead
  of buttons.
- `B` can now either swipe to toggle read/unread or swipe to delete the
  notification.
- `A` should receive a notification with the reply from `B`.
- `A` can either mark the answer as untrustworthy or accept it (trust it) via
  the normal buttons, as well as via the swipe left/right buttons.
- If `A` accepts the answer, then the status `Confirmed` is shown instead of
  buttons. On the other hand, if `A` marks as untrustworthy, then the status
  `Untrustworthy` is shown instead of buttons.
- `B` should receive no further notifications due to `A`s actions.
- `A` can now either swipe to toggle read/unread or swipe delete the
  notification.
2023-03-14 12:34:13 -03:00
Jamie Caprani e98ce54830 feat: add profile input to quo2 (#15323) 2023-03-14 06:52:15 -07:00
Alexander 4097aba39b Fetch details of user when adding a contact (#15070)
* Fixes

* Fixes

* Final fixes

* Lint fixes
2023-03-14 11:40:38 +01:00
Omar Basem 64dde1c9d1 feat: improve lightbox transition (#15315)
* feat: improve lightbox transition
2023-03-14 08:24:14 +04:00
flexsurfer 0fe6fea7e4 Improve home animations (#15247) 2023-03-13 19:47:04 +01:00
Ibrahem Khalil 4810e1d7bf Prevent #links to work except in community chats and allow redirection from channel to another when clicked (#15135) 2023-03-13 20:09:38 +02:00
Ulises Manuel Cárdenas 9e4d9a05a7 Add new onboarding screen - I'm new to status 2023-03-13 10:25:04 -06:00
Churikova Tetiana ee62ffdc2c e2e: AC actions 2023-03-13 14:29:26 +01:00
Jamie Caprani d8c110bf85 feat: add title input component to quo2 (#15133) 2023-03-11 08:29:11 -08:00
jakub ff3249c514 nix: add procps to default shell, and tcl for darwin
Cocoapod installations can fail with errors like:
```
tclsh /Users/jenkins/Library/Caches/CocoaPods/Pods/Release/SQLCipher/3.4.2-f9fcf/tool/addopcodes.tcl parse.h.temp >parse.h
./configure: line 11729: tclsh: command not found
./configure: line 12262: tclsh: command not found
./configure: line 12276: tclsh: command not found
```
If a pure shell is used. Also `pgrep` would be missing from a pure shell.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-03-10 16:19:11 +01:00
jakub 5380f83cab ci: ugprade Jenkins library to use narrow shell
Depends on:
https://github.com/status-im/status-jenkins-lib/pull/59

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-03-10 16:19:10 +01:00
Brian Sztamfater 7332f483a4 feat: integrate record audio component into composer
Signed-off-by: Brian Sztamfater <brian@status.im>
2023-03-10 12:05:12 -03:00
Mohamed Javid f67f205fa9 Status Tag UI and usage update (#15282)
* [Feature][#15267] Status Tag UI Update

* [Fix] Typo in comment

* [Fix] Feedback from PR

* [Fix] Feedback from PR

* [Fix] Blur Type on Preview Screen

* [Fix] Blur Type on Preview Screen
2023-03-10 15:31:54 +05:30
Yevheniia Berdnyk bd54b05fd0 test_community_mentions_push_notification 2023-03-09 20:37:52 +02:00
jakub 73c32eacb3 ios: add README file to ios/logs directory
Allows us to get rid of the `.gitkeep` file.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-03-09 14:55:01 +01:00
jakub a073812a9f ios: create logs/ios folder in advance
Otherwise in some cases we can get:
``
.../Script.sh: line 6: ./logs/react-native-xcode.log: No such file or directory
Command PhaseScriptExecution failed with a nonzero exit code
```

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-03-09 13:50:19 +01:00
jakub 8cd5e3f6e3 ios: direct logs to ios/logs, archive on failure
This way people don't have to ask me for access to logs from CI hosts to
be acquired using SSH access, but instead they will be simply included
in Jenkins build artifacts.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-03-09 12:23:51 +01:00
Churikova Tetiana 8d7224d972 e2e: fix failures 2023-03-09 12:06:24 +01:00
flexsurfer 81212573d7 🧈 Transparent modal support 🧈 (#15268) 2023-03-09 10:37:10 +01:00
Parvesh Monu de8c294c9a fix android multi-account root initialization after app relaunch (#15292) 2023-03-08 23:31:41 +05:30
Ulises Manuel Cárdenas 8478ac74ab Refactor user-avatar & add tests 2023-03-08 11:07:37 -06:00
Parvesh Monu e9310a2ace fix mention turns into unknown in community (#15285) 2023-03-08 20:15:05 +05:30
Churikova Tetiana 0345871b66 e2e: block user test 2023-03-08 12:21:10 +01:00
Ibrahem Khalil 7a299b9b86 [15166] Show message edited status (#15190) 2023-03-08 12:42:12 +02:00
yqrashawn 5fe3029ad8 feat: allow group admin to delete others message (#15256) 2023-03-08 10:08:40 +08:00
Roman Volosovskyi c6e63ff5a5 [#15241] Proper system message on being not a member of a group 2023-03-07 09:35:36 +01:00
228 changed files with 5532 additions and 2919 deletions
+2 -3
View File
@@ -100,9 +100,8 @@ Statusgo.xcframework
#ios #ios
/ios/Pods/ /ios/Pods/
/ios/logs/
/ios/StatusIm.xcworkspace /ios/StatusIm.xcworkspace
/ios/react-native-xcode.log
/ios/set_xcode_version.log
.ruby-version .ruby-version
status-e2e/ status-e2e/
@@ -183,4 +182,4 @@ test/appium/tests/users.py
/.calva/ /.calva/
## component-tests ## component-tests
*.log *.log
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
/* Options section can't access functions in objects. */ /* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild() def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
agent { label 'linux' } agent { label 'linux' }
+4 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
/* Options section can't access functions in objects. */ /* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild() def isPRBuild = utils.isPRBuild()
@@ -63,6 +63,9 @@ pipeline {
steps { steps {
script { api = ios.bundle() } script { api = ios.bundle() }
} }
post {
failure { archiveArtifacts 'ios/logs/*' }
}
} }
stage('Parallel Upload') { stage('Parallel Upload') {
parallel { parallel {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
agent { label params.AGENT_LABEL } agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
/* Options section can't access functions in objects. */ /* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild() def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
agent { label 'macos' } agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
agent { label 'linux' } agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.6' library 'status-jenkins-lib@v1.6.9'
pipeline { pipeline {
agent { agent {
+14
View File
@@ -116,6 +116,20 @@ deref'ed atoms.
{:background-color (colors/theme-colors colors/white colors/neutral-90)}) {: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 ### Using TODOs comments
_TODO_ comments are used extensively in the codebase, but prefer to use them _TODO_ comments are used extensively in the codebase, but prefer to use them
+3
View File
@@ -123,6 +123,7 @@ def build_ios_adhoc(readonly: false, pr_build: false)
export_method: 'ad-hoc', export_method: 'ad-hoc',
output_name: 'StatusIm', output_name: 'StatusIm',
output_directory: 'status-ios', output_directory: 'status-ios',
buildlog_path: 'ios/logs',
export_options: { export_options: {
signingStyle: 'manual', signingStyle: 'manual',
provisioningProfiles: { provisioningProfiles: {
@@ -162,6 +163,7 @@ def build_ios_e2e
# 3. directory where to up StatusIm.app # 3. directory where to up StatusIm.app
derived_data_path: 'status-ios', derived_data_path: 'status-ios',
output_name: 'StatusIm.app', output_name: 'StatusIm.app',
buildlog_path: 'ios/logs',
# ------------------------------------- # -------------------------------------
# Normal stuff # Normal stuff
scheme: 'StatusIm', scheme: 'StatusIm',
@@ -239,6 +241,7 @@ platform :ios do
clean: true, clean: true,
export_method: 'app-store', export_method: 'app-store',
output_directory: 'status-ios', output_directory: 'status-ios',
buildlog_path: 'ios/logs',
include_symbols: false, include_symbols: false,
export_options: { export_options: {
"combileBitcode": true, "combileBitcode": true,
+4 -4
View File
@@ -526,7 +526,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = "/usr/bin/env sh"; 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 > ./react-native-xcode.log 2>&1"; shellScript = "set -o errexit\nexport NODE_BINARY=\"${NODE_BINARY:-node}\"\nexport NODE_ARGS=\" --openssl-legacy-provider --max-old-space-size=16384 \"\n\nbash -x ../node_modules/react-native/scripts/react-native-xcode.sh > ./logs/react-native-xcode.log 2>&1";
}; };
0F876BD5356F61BF142A01A0 /* [CP] Check Pods Manifest.lock */ = { 0F876BD5356F61BF142A01A0 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
@@ -584,7 +584,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = "/usr/bin/env sh"; 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 > ./react-native-xcode.log 2>&1"; shellScript = "set -o errexit\nexport NODE_BINARY=\"${NODE_BINARY:-node}\"\nexport NODE_ARGS=\" --openssl-legacy-provider --max-old-space-size=16384 \"\n\nbash -x ../node_modules/react-native/scripts/react-native-xcode.sh > ./logs/react-native-xcode.log 2>&1";
}; };
3AAD2AD724A3A60E0075D594 /* Run Script */ = { 3AAD2AD724A3A60E0075D594 /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
@@ -598,7 +598,7 @@
); );
runOnlyForDeploymentPostprocessing = 1; runOnlyForDeploymentPostprocessing = 1;
shellPath = "/usr/bin/env sh"; shellPath = "/usr/bin/env sh";
shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./set_xcode_version.log 2>&1"; shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./logs/set_xcode_version.log 2>&1";
}; };
3C1038075AE5E6FB86AC2319 /* [CP] Copy Pods Resources */ = { 3C1038075AE5E6FB86AC2319 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
@@ -678,7 +678,7 @@
); );
runOnlyForDeploymentPostprocessing = 1; runOnlyForDeploymentPostprocessing = 1;
shellPath = "/usr/bin/env sh"; shellPath = "/usr/bin/env sh";
shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./set_xcode_version.log 2>&1"; shellScript = "\"${PROJECT_DIR}/scripts/set_xcode_version.sh\" > ./logs/set_xcode_version.log 2>&1";
}; };
E732E3E1B024946173BF6D3D /* [CP] Copy Pods Resources */ = { E732E3E1B024946173BF6D3D /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
+14
View File
@@ -0,0 +1,14 @@
# 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`.
+2 -2
View File
@@ -12,7 +12,7 @@ in mkShell {
buildInputs = with pkgs; lib.unique ([ buildInputs = with pkgs; lib.unique ([
# core utilities that should always be present in a shell # core utilities that should always be present in a shell
bash curl wget file unzip flock bash curl wget file unzip flock procps
git gnumake jq ncurses gnugrep parallel git gnumake jq ncurses gnugrep parallel
lsof # used in start-react-native.sh lsof # used in start-react-native.sh
# build specific utilities # build specific utilities
@@ -22,7 +22,7 @@ in mkShell {
# other nice to have stuff # other nice to have stuff
yarn nodejs python27 yarn nodejs python27
] # and some special cases ] # and some special cases
++ lib.optionals stdenv.isDarwin [ cocoapods clang ] ++ lib.optionals stdenv.isDarwin [ cocoapods clang tcl ]
++ lib.optionals (!stdenv.isDarwin) [ gcc8 ] ++ lib.optionals (!stdenv.isDarwin) [ gcc8 ]
); );
Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

+10
View File
@@ -0,0 +1,10 @@
import { useDerivedValue } from 'react-native-reanimated';
export function infoLayout(input, isTop) {
return useDerivedValue(
function () {
'worklet'
return isTop ? input.value : -input.value
}
);
}
+7
View File
@@ -0,0 +1,7 @@
import {useAnimatedScrollHandler} from "react-native-reanimated";
export function useAnimatedScrollHandlerWorklet(scrollY) {
return useAnimatedScrollHandler((event) => {
scrollY.value = event.contentOffset.y;
})
}
+10 -10
View File
@@ -147,7 +147,10 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
:SvgXml #js {:render identity} :SvgXml #js {:render identity}
:default #js {:render identity}}) :default #js {:render identity}})
(def react-native-webview #js {:default {}}) (def react-native-webview #js {:default {}})
(def react-native-audio-toolkit #js {:MediaStates {}}) (def react-native-audio-toolkit
#js
{:MediaStates {}
:PlaybackCategories {}})
(def net-info #js {}) (def net-info #js {})
(def touchid #js {}) (def touchid #js {})
(def react-native-image-viewing #js {:default {}}) (def react-native-image-viewing #js {:default {}})
@@ -352,11 +355,6 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
(def worklet-factory (def worklet-factory
#js {:applyAnimationsToStyle (fn [])}) #js {:applyAnimationsToStyle (fn [])})
(def shell-worklets #js {})
(def bottom-sheet #js {})
(def record-audio-worklets #js {})
;; Update i18n_resources.cljs ;; Update i18n_resources.cljs
(defn mock (defn mock
[module] [module]
@@ -404,10 +402,12 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
"@react-native-async-storage/async-storage" async-storage "@react-native-async-storage/async-storage" async-storage
"react-native-svg" react-native-svg "react-native-svg" react-native-svg
"react-native-orientation-locker" react-native-orientation-locker "react-native-orientation-locker" react-native-orientation-locker
"../src/js/worklet_factory.js" worklet-factory "../src/js/worklets/core.js" worklet-factory
"../src/js/shell_worklets.js" shell-worklets "../src/js/worklets/shell.js" #js {}
"../src/js/bottom_sheet.js" bottom-sheet "../src/js/worklets/bottom_sheet.js" #js {}
"../src/js/record_audio_worklets.js" record-audio-worklets "../src/js/worklets/record_audio.js" #js {}
"../src/js/worklets/scroll_view.js" #js {}
"../src/js/worklets/lightbox.js" #js {}
"./fleets.js" default-fleets "./fleets.js" default-fleets
"@walletconnect/client" wallet-connect-client "@walletconnect/client" wallet-connect-client
"../translations/ar.json" (js/JSON.parse (slurp "./translations/ar.json")) "../translations/ar.json" (js/JSON.parse (slurp "./translations/ar.json"))
@@ -1,138 +0,0 @@
(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?)}])]))
@@ -0,0 +1,129 @@
(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))))))
@@ -0,0 +1,83 @@
(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}))
@@ -0,0 +1,61 @@
(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?)}])]))
+5 -4
View File
@@ -204,7 +204,7 @@
[button opts \"label\"] [button opts \"label\"]
opts opts
{:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/ {:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/
:danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell/:community :danger/:photo-bg/:blur-bg/:blur-bg-outline/:shell/:community
:size 40/32/24 :size 40/32/24
:icon true/false :icon true/false
:community-color '#FFFFFF' :community-color '#FFFFFF'
@@ -213,14 +213,14 @@
:after :icon-keyword} :after :icon-keyword}
only icon only icon
[button {:icon true} :main-icons/close-circle]" [button {:icon true} :i/close-circle]"
[_ _] [_ _]
(let [pressed (reagent/atom false)] (let [pressed (reagent/atom false)]
(fn (fn
[{:keys [on-press disabled type size community-color community-text-color before after above [{:keys [on-press disabled type size community-color community-text-color before after above
width width
override-theme override-background-color override-theme override-background-color
on-long-press accessibility-label icon icon-no-color style test-ID] on-long-press accessibility-label icon icon-no-color style inner-style test-ID]
:or {type :primary :or {type :primary
size 40}} size 40}}
children] children]
@@ -275,7 +275,8 @@
(community-themed? type community-color) (community-themed? type community-color)
(merge (merge
{:background-color community-color} {:background-color community-color}
(when (= state :pressed) {:opacity 0.9}))))} (when (= state :pressed) {:opacity 0.9})))
inner-style)}
(when above (when above
[rn/view [rn/view
[quo2.icons/icon above [quo2.icons/icon above
@@ -78,42 +78,42 @@
[props [props
{:keys [name {:keys [name
muted? muted?
unread-messages? unviewed-messages-count
unread-mentions-count unviewed-mentions-count
status status
community-icon images
tokens tokens
locked?]}] locked?
style]}]
[rn/touchable-highlight [rn/touchable-highlight
(merge {:underlay-color (colors/theme-colors (merge {:underlay-color (colors/theme-colors
colors/neutral-5 colors/neutral-5
colors/neutral-95) colors/neutral-95)
:style {:border-radius 12}} :style {:border-radius 12}}
props) props)
[rn/view {:flex 1} [rn/view (merge (style/membership-info-container) style)
[rn/view (style/membership-info-container) [community-icon/community-icon
[community-icon/community-icon {:images images} 32]
{:images community-icon} 32] [rn/view
[rn/view {:flex 1
{:flex 1 :margin-left 12
:margin-left 12 :justify-content :center}
:justify-content :center} [text/text
[text/text {:accessibility-label :chat-name-text
{:accessibility-label :chat-name-text :number-of-lines 1
:number-of-lines 1 :ellipsize-mode :tail
:ellipsize-mode :tail :weight :semi-bold
:weight :semi-bold :size :paragraph-1}
:size :paragraph-1} name]]
name]]
[rn/view [rn/view
{:justify-content :center {:justify-content :center
:margin-right 16} :margin-right 16}
(if (= status :gated) (if (= status :gated)
[community-view/permission-tag-container [community-view/permission-tag-container
{:locked? locked? {:locked? locked?
:tokens tokens}] :tokens tokens}]
[notification-view [notification-view
{:muted? muted? {:muted? muted?
:unread-mentions-count unread-mentions-count :unread-mentions-count unviewed-mentions-count
:unread-messages? unread-messages?}])]]]]) :unread-messages? (pos? unviewed-messages-count)}])]]])
@@ -92,6 +92,7 @@
colors/neutral-90)} colors/neutral-90)}
{:flex-direction :row {:flex-direction :row
:margin-horizontal 20 :margin-horizontal 20
:margin-vertical 8
:height 56 :height 56
:padding-right 12}) :padding-right 12})
[card-title-and-description title description] [card-title-and-description title description]
+1 -1
View File
@@ -15,9 +15,9 @@
:shadow-radius radius :shadow-radius radius
:shadow-opacity 1 :shadow-opacity 1
:shadow-color colors/shadow :shadow-color colors/shadow
:elevation 1
:border-radius radius :border-radius radius
:justify-content :space-between :justify-content :space-between
:elevation 2
:background-color (colors/theme-colors :background-color (colors/theme-colors
colors/white colors/white
colors/neutral-90)}) colors/neutral-90)})
@@ -1,34 +1,29 @@
(ns quo2.components.counter.--tests--.counter-component-spec (ns quo2.components.counter.--tests--.counter-component-spec
(:require ["@testing-library/react-native" :as rtl] (:require [quo2.components.counter.counter :as counter]
[quo2.components.counter.counter :as counter] [test-helpers.component :as h]))
[reagent.core :as reagent]))
(defn render-counter (h/describe "counter component"
([] (h/test "default render of counter component"
(render-counter {} nil)) (h/render [counter/counter {} nil])
([opts value] (-> (h/expect (h/get-by-test-id :counter-component))
(rtl/render (reagent/as-element [counter/counter opts value])))) (h/is-truthy)))
(js/global.test "default render of counter component" (h/test "renders counter with a string value"
(fn [] (h/render [counter/counter {} "1"])
(render-counter) (-> (h/expect (h/get-by-text "1"))
(-> (js/expect (rtl/screen.getByTestId "counter-component")) (h/is-truthy)))
(.toBeTruthy))))
(js/global.test "renders counter with a string value" (h/test "renders counter with an integer value"
(fn [] (h/render [counter/counter {} 1])
(render-counter {} "1") (-> (h/expect (h/get-by-text "1"))
(-> (js/expect (rtl/screen.getByText "1")) (h/is-truthy)))
(.toBeTruthy))))
(js/global.test "renders counter with an integer value" (h/test "renders counter with max value 99+ by default"
(fn [] (h/render [counter/counter {} 100])
(render-counter {} 1) (-> (h/expect (h/get-by-text "99+"))
(-> (js/expect (rtl/screen.getByText "1")) (h/is-truthy)))
(.toBeTruthy))))
(js/global.test "renders counter with value 99+ when the value is greater than 99" (h/test "renders counter with custom max value when set to 150"
(fn [] (h/render [counter/counter {:max-value 150} 151])
(render-counter {} "100") (-> (h/expect (h/get-by-text "150+"))
(-> (js/expect (rtl/screen.getByText "99+")) (h/is-truthy))))
(.toBeTruthy))))
+13 -13
View File
@@ -1,8 +1,10 @@
(ns quo2.components.counter.counter (ns quo2.components.counter.counter
(:require [quo2.components.markdown.text :as text] (:require
[quo2.foundations.colors :as colors] [quo2.components.markdown.text :as text]
[quo2.theme :as theme] [quo2.foundations.colors :as colors]
[react-native.core :as rn])) [quo2.theme :as theme]
[react-native.core :as rn]
[utils.number :as utils-number]))
(def themes (def themes
{:light {:default colors/primary-50 {:light {:default colors/primary-50
@@ -19,9 +21,9 @@
(get-in themes [(theme/get-theme) key])) (get-in themes [(theme/get-theme) key]))
(defn counter (defn counter
"type: default, secondary, grey, outline [{:keys [type override-text-color override-bg-color style accessibility-label max-value]
value: integer" :or {max-value 99}}
[{:keys [type override-text-color override-bg-color style accessibility-label]} value] value]
(let [type (or type :default) (let [type (or type :default)
text-color (or override-text-color text-color (or override-text-color
(if (or (if (or
@@ -29,11 +31,9 @@
(= type :default)) (= type :default))
colors/white colors/white
colors/neutral-100)) colors/neutral-100))
value (if (integer? value) value (utils-number/parse-int value)
value label (if (> value max-value)
(js/parseInt value)) (str max-value "+")
label (if (> value 99)
"99+"
(str value)) (str value))
width (case (count label) width (case (count label)
1 16 1 16
@@ -59,7 +59,7 @@
(or override-bg-color (or override-bg-color
(get-color type))) (get-color type)))
(> value 99) (> value max-value)
(assoc :padding-left 0.5))} (assoc :padding-left 0.5))}
[text/text [text/text
{:weight :medium {:weight :medium
@@ -1,4 +1,4 @@
(ns quo2.components.input.style (ns quo2.components.inputs.input.style
(:require [quo2.components.markdown.text :as text] (:require [quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors])) [quo2.foundations.colors :as colors]))
@@ -1,7 +1,7 @@
(ns quo2.components.input.view (ns quo2.components.inputs.input.view
(:require [oops.core :as oops] (:require [oops.core :as oops]
[quo2.components.icon :as icon] [quo2.components.icon :as icon]
[quo2.components.input.style :as style] [quo2.components.inputs.input.style :as style]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent])) [reagent.core :as reagent]))
@@ -0,0 +1,29 @@
(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)))))
@@ -0,0 +1,29 @@
(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})
@@ -0,0 +1,51 @@
(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})]]]))
@@ -0,0 +1,40 @@
(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)))))
@@ -0,0 +1,52 @@
(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})
@@ -0,0 +1,65 @@
(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 (ns quo2.components.list-items.preview-list
(:require [quo2.components.avatars.user-avatar :as user-avatar] (:require [quo2.components.avatars.user-avatar.view :as user-avatar]
[quo2.components.icon :as quo2.icons] [quo2.components.icon :as quo2.icons]
[quo2.components.markdown.text :as quo2.text] [quo2.components.markdown.text :as quo2.text]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
@@ -1,6 +1,6 @@
(ns quo2.components.list-items.user-list (ns quo2.components.list-items.user-list
(:require [react-native.core :as rn] (:require [react-native.core :as rn]
[quo2.components.avatars.user-avatar :as user-avatar] [quo2.components.avatars.user-avatar.view :as user-avatar]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.components.icon :as icons] [quo2.components.icon :as icons]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
@@ -1,6 +1,6 @@
(ns quo2.components.messages.system-message (ns quo2.components.messages.system-message
(:require [quo2.components.avatars.icon-avatar :as icon-avatar] (:require [quo2.components.avatars.icon-avatar :as icon-avatar]
[quo2.components.avatars.user-avatar :as user-avatar] [quo2.components.avatars.user-avatar.view :as user-avatar]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[quo2.theme :as theme] [quo2.theme :as theme]
+20 -11
View File
@@ -1,6 +1,6 @@
(ns quo2.components.navigation.page-nav (ns quo2.components.navigation.page-nav
(:require [clojure.string :as string] (:require [clojure.string :as string]
[quo2.components.avatars.user-avatar :as user-avatar] [quo2.components.avatars.user-avatar.view :as user-avatar]
[quo2.components.buttons.button :as button] [quo2.components.buttons.button :as button]
[quo2.components.icon :as icons] [quo2.components.icon :as icons]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
@@ -30,7 +30,7 @@
{:no-color true}))) {:no-color true})))
(defn left-section-view (defn left-section-view
[{:keys [on-press icon accessibility-label type] :or {type :grey}} [{:keys [on-press icon accessibility-label type icon-override-theme] :or {type :grey}}
put-middle-section-on-left?] put-middle-section-on-left?]
[rn/view {:style (when put-middle-section-on-left? {:margin-right 5})} [rn/view {:style (when put-middle-section-on-left? {:margin-right 5})}
[button/button [button/button
@@ -38,7 +38,8 @@
:icon true :icon true
:type type :type type
:size 32 :size 32
:accessibility-label accessibility-label} :accessibility-label accessibility-label
:override-theme icon-override-theme}
icon]]) icon]])
(defn- mid-section-comp (defn- mid-section-comp
@@ -148,10 +149,20 @@
:flex-direction :row :flex-direction :row
:justify-content :flex-end)} :justify-content :flex-end)}
(let [last-icon-index (-> right-section-buttons count dec)] (let [last-icon-index (-> right-section-buttons count dec)]
(map-indexed (fn [index {:keys [icon on-press type style] :or {type :grey}}] (map-indexed (fn [index
{:keys [icon on-press type style icon-override-theme]
:or {type :grey}}]
^{:key index} ^{:key index}
[rn/view {:style (merge {:margin-right (if (not= index last-icon-index) 8 0)} style)} [rn/view
[button/button {:on-press on-press :icon true :type type :size 32} {: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}
icon]]) icon]])
right-section-buttons))]) right-section-buttons))])
@@ -179,16 +190,14 @@
:left-section :left-section
{:type button-type {:type button-type
:on-press event :on-press event
:icon icon :icon icon
:icon-color color :icon-override-theme :light/:dark
:icon-background-color color
} }
:right-section-buttons vector of :right-section-buttons vector of
{:type button-type {:type button-type
:on-press event :on-press event
:icon icon :icon icon
:icon-color color :icon-override-theme :light/:dark
:icon-background-color color
} }
} }
" "
@@ -7,9 +7,9 @@
[quo2.components.notifications.activity-log.style :as style] [quo2.components.notifications.activity-log.style :as style]
[quo2.components.tags.status-tags :as status-tags] [quo2.components.tags.status-tags :as status-tags]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[utils.i18n :as i18n]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent])) [reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn- activity-reply-text-input (defn- activity-reply-text-input
[{:keys [on-update-reply max-reply-length valid-reply?]} reply-input] [{:keys [on-update-reply max-reply-length valid-reply?]} reply-input]
@@ -37,7 +37,7 @@
(when on-update-reply (when on-update-reply
(on-update-reply %))) (on-update-reply %)))
:auto-capitalize :none :auto-capitalize :none
:auto-focus true :auto-focus false
:accessibility-label :identity-verification-reply-text-input :accessibility-label :identity-verification-reply-text-input
:placeholder (i18n/label :t/type-something) :placeholder (i18n/label :t/type-something)
:return-key-type :none :return-key-type :none
@@ -129,19 +129,20 @@
(-> button (-> button
(assoc :size size) (assoc :size size)
(assoc :type subtype) (assoc :type subtype)
(assoc :disabled (and replying? (disable-when @reply-input))) (assoc :disabled (and replying? disable-when (disable-when @reply-input)))
(update :style merge common-style {:margin-right 8})) (update :style merge common-style {:margin-right 8}))
label])) label]))
(defmethod footer-item-view :status (defmethod footer-item-view :status
[{:keys [label subtype]} _ _] [{:keys [label subtype blur?]} _ _]
[status-tags/status-tag [status-tags/status-tag
{:size :small {:size :small
:label label :label label
:status {:type subtype}}]) :status {:type subtype}
:blur? blur?}])
(defn- footer (defn- footer
[_ _] [_]
(let [reply-input (reagent/atom "")] (let [reply-input (reagent/atom "")]
(fn [{:keys [replying? items] :as props}] (fn [{:keys [replying? items] :as props}]
[:<> [:<>
@@ -1,6 +1,9 @@
(ns quo2.components.onboarding.small-option-card.style (ns quo2.components.onboarding.small-option-card.style
(:require [quo2.foundations.colors :as colors])) (:require [quo2.foundations.colors :as colors]))
(def main-variant-height 335)
(def icon-variant-height 56)
(def text-container {:flex 1}) (def text-container {:flex 1})
(def title (def title
@@ -41,12 +44,10 @@
(def touchable-overlay {:border-radius 16}) (def touchable-overlay {:border-radius 16})
(defn card-container
[main-variant?]
(when main-variant? {:height 343}))
(defn card (defn card
[main-variant?] [height]
{:background-color colors/white-opa-5 {:background-color colors/white-opa-5
:border-radius 16 :border-radius 16
:height (if main-variant? 335 56)}) :height height})
(def main-variant-extra-space {:height 8})
@@ -35,7 +35,7 @@
:subtitle subtitle}]]) :subtitle subtitle}]])
(defn- main-variant (defn- main-variant
[{:keys [title subtitle image]}] [{:keys [title subtitle image max-height]}]
[rn/view {:style style/main-variant} [rn/view {:style style/main-variant}
[rn/view {:style style/main-variant-text-container} [rn/view {:style style/main-variant-text-container}
[texts [texts
@@ -43,24 +43,32 @@
:subtitle subtitle}]] :subtitle subtitle}]]
[fast-image/fast-image [fast-image/fast-image
{:accessibility-label :small-option-card-main-image {:accessibility-label :small-option-card-main-image
:style {:flex 1} :style {:flex 1 :max-height max-height}
:resize-mode :contain :resize-mode :contain
:source image}]]) :source image}]])
(defn small-option-card (defn small-option-card
[{:keys [variant title subtitle image on-press] "Variants: `:main` or `:icon`"
[{:keys [variant title subtitle image max-height on-press]
:or {variant :main}}] :or {variant :main}}]
(let [main-variant? (= variant :main) (let [main-variant? (= variant :main)
card-component (if main-variant? main-variant icon-variant)] card-component (if main-variant? main-variant icon-variant)
[rn/touchable-highlight card-height (cond
{:accessibility-label :small-option-card (not main-variant?) style/icon-variant-height
:style style/touchable-overlay max-height (min max-height style/main-variant-height)
:active-opacity 1 :else style/main-variant-height)]
:underlay-color colors/white-opa-5 [rn/view
:on-press on-press} [rn/touchable-highlight
[rn/view {:style (style/card-container main-variant?)} {:accessibility-label :small-option-card
[rn/view {:style (style/card main-variant?)} :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 [card-component
{:title title {:title title
:subtitle subtitle :subtitle subtitle
:image image}]]]])) :image image
:max-height max-height}]]]
(when main-variant?
[rn/view {:style style/main-variant-extra-space}])]))
@@ -6,7 +6,7 @@
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button] [quo2.components.buttons.button :as button]
[quo2.components.avatars.user-avatar :as user-avatar] [quo2.components.avatars.user-avatar.view :as user-avatar]
[quo2.components.profile.profile-card.style :as style])) [quo2.components.profile.profile-card.style :as style]))
(defn profile-card (defn profile-card
@@ -3,7 +3,7 @@
[quo2.components.profile.select-profile.style :as style] [quo2.components.profile.select-profile.style :as style]
[react-native.core :as rn] [react-native.core :as rn]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.components.avatars.user-avatar :as user-avatar] [quo2.components.avatars.user-avatar.view :as user-avatar]
[reagent.core :as reagent])) [reagent.core :as reagent]))
(defn- on-change-handler (defn- on-change-handler
@@ -27,7 +27,8 @@
:on-start-should-set-responder :on-start-should-set-responder
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 0}})
(-> (h/expect event) (-> (h/expect event)
(.toHaveBeenCalledTimes 1)))) (.toHaveBeenCalledTimes 1))))
@@ -36,19 +37,23 @@
(h/render [record-audio/record-audio (h/render [record-audio/record-audio
{:on-reviewing-audio event {:on-reviewing-audio event
:record-audio-permission-granted true}]) :record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _] (with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))] (on-start))
audio/get-recorder-file-path (fn [] "file-path")]
(h/fire-event (h/fire-event
:on-start-should-set-responder :on-start-should-set-responder
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500) (h/advance-timers-by-time 500)
(h/fire-event (h/fire-event
:on-responder-release :on-responder-release
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (h/expect event) (-> (h/expect event)
(.toHaveBeenCalledTimes 1))))) (.toHaveBeenCalledTimes 1)))))
@@ -59,27 +64,40 @@
:record-audio-permission-granted true}]) :record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _] (with-redefs [audio/start-recording (fn [_ on-start _]
(on-start)) (on-start))
audio/get-recorder-file-path (fn [] "audio-file-path")] audio/get-recorder-file-path (fn [] "audio-file-path")
audio/get-player-duration (fn [] 5000)]
(h/fire-event (h/fire-event
:on-start-should-set-responder :on-start-should-set-responder
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500) (h/advance-timers-by-time 500)
(h/fire-event (h/fire-event
:on-responder-release :on-responder-release
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 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)
(h/fire-event (h/fire-event
:on-responder-release :on-responder-release
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 80 {:nativeEvent {:locationX 80
:locationY 80}}) :locationY 80
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event) (-> (js/expect event)
(.toHaveBeenCalledTimes 1)) (.toHaveBeenCalledTimes 1))
(-> (js/expect event) (-> (js/expect event)
(.toHaveBeenCalledWith "audio-file-path"))))) (.toHaveBeenCalledWith {:file-path "audio-file-path"
:duration 5000})))))
(h/test "record-audio on-send works after sliding to the send button" (h/test "record-audio on-send works after sliding to the send button"
(let [event (js/jest.fn)] (let [event (js/jest.fn)]
@@ -95,7 +113,8 @@
:on-start-should-set-responder :on-start-should-set-responder
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500) (h/advance-timers-by-time 500)
(h/fire-event (h/fire-event
:on-responder-move :on-responder-move
@@ -108,11 +127,14 @@
:on-responder-release :on-responder-release
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 40 {:nativeEvent {:locationX 40
:locationY 80}}) :locationY 80
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event) (-> (js/expect event)
(.toHaveBeenCalledTimes 1)) (.toHaveBeenCalledTimes 1))
(-> (js/expect event) (-> (js/expect event)
(.toHaveBeenCalledWith "audio-file-path"))))) (.toHaveBeenCalledWith {:file-path "audio-file-path"
:duration 500})))))
(h/test "record-audio on-cancel works after reviewing audio" (h/test "record-audio on-cancel works after reviewing audio"
(let [event (js/jest.fn)] (let [event (js/jest.fn)]
@@ -125,18 +147,22 @@
:on-start-should-set-responder :on-start-should-set-responder
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500) (h/advance-timers-by-time 500)
(h/fire-event (h/fire-event
:on-responder-release :on-responder-release
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 200}})
(h/fire-event (h/fire-event
:on-responder-release :on-responder-release
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 40 {:nativeEvent {:locationX 40
:locationY 80}}) :locationY 80
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event) (-> (js/expect event)
(.toHaveBeenCalledTimes 1))))) (.toHaveBeenCalledTimes 1)))))
@@ -153,7 +179,8 @@
:on-start-should-set-responder :on-start-should-set-responder
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70 {:nativeEvent {:locationX 70
:locationY 70}}) :locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500) (h/advance-timers-by-time 500)
(h/fire-event (h/fire-event
:on-responder-move :on-responder-move
@@ -166,6 +193,8 @@
:on-responder-release :on-responder-release
(h/get-by-test-id "record-audio") (h/get-by-test-id "record-audio")
{:nativeEvent {:locationX -10 {:nativeEvent {:locationX -10
:locationY 70}}) :locationY 70
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event) (-> (js/expect event)
(.toHaveBeenCalledTimes 1)))))) (.toHaveBeenCalledTimes 1))))))
@@ -4,58 +4,60 @@
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated] [react-native.reanimated :as reanimated]
[react-native.core :refer [use-effect]] [react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :refer [quo2.components.record-audio.record-audio.helpers :as helpers]))
[animate-linear-with-delay
animate-easing-with-delay
animate-linear
set-value]]))
(defn delete-button (defn delete-button
[recording? ready-to-delete? reviewing-audio?] [recording? ready-to-delete? reviewing-audio? force-show-controls?]
[:f> [:f>
(fn [] (fn []
(let [opacity (reanimated/use-shared-value 0) (let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-x (reanimated/use-shared-value 20) translate-x (reanimated/use-shared-value (if force-show-controls? 35 20))
scale (reanimated/use-shared-value 1) scale (reanimated/use-shared-value (if force-show-controls? 0.75 1))
connector-opacity (reanimated/use-shared-value 0) connector-opacity (reanimated/use-shared-value 0)
connector-width (reanimated/use-shared-value 24) connector-width (reanimated/use-shared-value 24)
connector-height (reanimated/use-shared-value 12) connector-height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8) border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8) border-radius-second-half (reanimated/use-shared-value 8)
start-x-animation (fn [] start-x-animation (fn []
(animate-linear-with-delay translate-x 12 50 133.33) (helpers/animate-linear-with-delay translate-x 12 50 133.33)
(animate-easing-with-delay connector-opacity 1 0 93.33) (helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(animate-easing-with-delay connector-width 56 83.33 80) (helpers/animate-easing-with-delay connector-width 56 83.33 80)
(animate-easing-with-delay connector-height 56 83.33 80) (helpers/animate-easing-with-delay connector-height 56 83.33 80)
(animate-easing-with-delay border-radius-first-half 28 83.33 80) (helpers/animate-easing-with-delay border-radius-first-half
(animate-easing-with-delay border-radius-second-half 28 83.33 80)) 28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-x-animation (fn [] reset-x-animation (fn []
(animate-linear translate-x 0 100) (helpers/animate-linear translate-x 0 100)
(set-value connector-opacity 0) (helpers/set-value connector-opacity 0)
(set-value connector-width 24) (helpers/set-value connector-width 24)
(set-value connector-height 12) (helpers/set-value connector-height 12)
(set-value border-radius-first-half 8) (helpers/set-value border-radius-first-half 8)
(set-value border-radius-second-half 16)) (helpers/set-value border-radius-second-half 16))
fade-in-animation (fn [] fade-in-animation (fn []
(animate-linear translate-x 0 200) (helpers/animate-linear translate-x 0 200)
(animate-linear opacity 1 200)) (helpers/animate-linear opacity 1 200))
fade-out-animation (fn [] fade-out-animation (fn []
(animate-linear (helpers/animate-linear
translate-x translate-x
(if @reviewing-audio? 35 20) (if @reviewing-audio? 35 20)
200) 200)
(if @reviewing-audio? (if @reviewing-audio?
(animate-linear scale 0.75 200) (helpers/animate-linear scale 0.75 200)
(animate-linear opacity 0 200)) (helpers/animate-linear opacity 0 200))
(set-value connector-opacity 0) (helpers/set-value connector-opacity 0)
(set-value connector-width 24) (helpers/set-value connector-width 24)
(set-value connector-height 12) (helpers/set-value connector-height 12)
(set-value border-radius-first-half 8) (helpers/set-value border-radius-first-half 8)
(set-value border-radius-second-half 16)) (helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn [] fade-out-reset-animation (fn []
(animate-linear opacity 0 200) (helpers/animate-linear opacity 0 200)
(animate-linear-with-delay translate-x 20 0 200) (helpers/animate-linear-with-delay translate-x 20 0 200)
(animate-linear-with-delay scale 1 0 200))] (helpers/animate-linear-with-delay scale 1 0 200))]
(use-effect (fn [] (use-effect (fn []
(if @recording? (if @recording?
(fade-in-animation) (fade-in-animation)
@@ -4,11 +4,7 @@
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated] [react-native.reanimated :as reanimated]
[react-native.core :refer [use-effect]] [react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :refer [quo2.components.record-audio.record-audio.helpers :as helpers]))
[animate-linear-with-delay
animate-easing-with-delay
animate-linear
set-value]]))
(defn lock-button (defn lock-button
[recording? ready-to-lock? locked?] [recording? ready-to-lock? locked?]
@@ -22,36 +18,36 @@
border-radius-first-half (reanimated/use-shared-value 8) border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8) border-radius-second-half (reanimated/use-shared-value 8)
start-x-y-animation (fn [] start-x-y-animation (fn []
(animate-linear-with-delay translate-x-y 8 50 116.66) (helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
(animate-easing-with-delay connector-opacity 1 0 80) (helpers/animate-easing-with-delay connector-opacity 1 0 80)
(animate-easing-with-delay width 56 83.33 63.33) (helpers/animate-easing-with-delay width 56 83.33 63.33)
(animate-easing-with-delay height 56 83.33 63.33) (helpers/animate-easing-with-delay height 56 83.33 63.33)
(animate-easing-with-delay border-radius-first-half (helpers/animate-easing-with-delay border-radius-first-half
28 28
83.33 83.33
63.33) 63.33)
(animate-easing-with-delay border-radius-second-half (helpers/animate-easing-with-delay border-radius-second-half
28 28
83.33 83.33
63.33)) 63.33))
reset-x-y-animation (fn [] reset-x-y-animation (fn []
(animate-linear translate-x-y 0 100) (helpers/animate-linear translate-x-y 0 100)
(set-value connector-opacity 0) (helpers/set-value connector-opacity 0)
(set-value width 24) (helpers/set-value width 24)
(set-value height 12) (helpers/set-value height 12)
(set-value border-radius-first-half 8) (helpers/set-value border-radius-first-half 8)
(set-value border-radius-second-half 16)) (helpers/set-value border-radius-second-half 16))
fade-in-animation (fn [] fade-in-animation (fn []
(animate-linear translate-x-y 0 220) (helpers/animate-linear translate-x-y 0 220)
(animate-linear opacity 1 220)) (helpers/animate-linear opacity 1 220))
fade-out-animation (fn [] fade-out-animation (fn []
(animate-linear translate-x-y 20 200) (helpers/animate-linear translate-x-y 20 200)
(animate-linear opacity 0 200) (helpers/animate-linear opacity 0 200)
(set-value connector-opacity 0) (helpers/set-value connector-opacity 0)
(set-value width 24) (helpers/set-value width 24)
(set-value height 12) (helpers/set-value height 12)
(set-value border-radius-first-half 8) (helpers/set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))] (helpers/set-value border-radius-second-half 16))]
(use-effect (fn [] (use-effect (fn []
(if @recording? (if @recording?
(fade-in-animation) (fade-in-animation)
@@ -5,15 +5,15 @@
[react-native.core :as rn :refer [use-effect]] [react-native.core :as rn :refer [use-effect]]
[react-native.reanimated :as reanimated] [react-native.reanimated :as reanimated]
[quo2.components.buttons.button :as button] [quo2.components.buttons.button :as button]
[quo2.components.record-audio.record-audio.helpers :refer [set-value]])) [quo2.components.record-audio.record-audio.helpers :as helpers]))
(defn record-button (defn record-button
[recording? reviewing-audio?] [recording? reviewing-audio?]
[:f> [:f>
(fn [] (fn []
(let [opacity (reanimated/use-shared-value 1) (let [opacity (reanimated/use-shared-value 1)
show-animation #(set-value opacity 1) show-animation #(helpers/set-value opacity 1)
hide-animation #(set-value opacity 0)] hide-animation #(helpers/set-value opacity 0)]
(use-effect (fn [] (use-effect (fn []
(if (or @recording? @reviewing-audio?) (if (or @recording? @reviewing-audio?)
(hide-animation) (hide-animation)
@@ -25,4 +25,4 @@
:size 32 :size 32
:width 32 :width 32
:accessibility-label :mic-button} :accessibility-label :mic-button}
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-100 colors/white)}]]]))]) [icons/icon :i/audio {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]]))])
@@ -8,12 +8,8 @@
[taoensso.timbre :as log] [taoensso.timbre :as log]
[cljs-bean.core :as bean] [cljs-bean.core :as bean]
[reagent.core :as reagent] [reagent.core :as reagent]
[quo2.components.record-audio.record-audio.helpers :refer [quo2.components.record-audio.record-audio.helpers :as helpers]
[animate-linear [utils.worklets.record-audio :as worklets.record-audio]))
animate-linear-with-delay
animate-linear-with-delay-loop
animate-easing
set-value]]))
(def ^:private scale-to-each 1.8) (def ^:private scale-to-each 1.8)
(def ^:private scale-to-total 2.6) (def ^:private scale-to-total 2.6)
@@ -23,14 +19,6 @@
(def ^:private signal-anim-duration 3900) (def ^:private signal-anim-duration 3900)
(def ^:private signal-anim-duration-2 1950) (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 (def ^:private animated-ring
(reagent/adapt-react-class (reagent/adapt-react-class
(rn/memo (rn/memo
@@ -45,129 +33,133 @@
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel] clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]
[:f> [:f>
(fn [] (fn []
(let [scale (reanimated/use-shared-value 1) (let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0) opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default) opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map animations (map
(fn [index] (fn [index]
(let [ring-scale (ring-scale scale (* scale-padding index))] (let [ring-scale (worklets.record-audio/ring-scale scale
{:scale ring-scale (* scale-padding
:opacity (reanimated/interpolate ring-scale index))]
[1 scale-to-each] {:scale ring-scale
[opacity-from 0])})) :opacity (reanimated/interpolate ring-scale
(range 0 5)) [1 scale-to-each]
rings-color (cond [opacity-from 0])}))
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque (range 0 5))
colors/neutral-80) rings-color (cond
@ready-to-delete? colors/danger-50 @ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
:else colors/primary-50) colors/neutral-80)
translate-y (reanimated/use-shared-value 0) @ready-to-delete? colors/danger-50
translate-x (reanimated/use-shared-value 0) :else colors/primary-50)
button-color colors/primary-50 translate-y (reanimated/use-shared-value 0)
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white) translate-x (reanimated/use-shared-value 0)
icon-opacity (reanimated/use-shared-value 1) button-color colors/primary-50
red-overlay-opacity (reanimated/use-shared-value 0) 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) gray-overlay-opacity (reanimated/use-shared-value 0)
complete-animation (fn [] complete-animation
(cond (fn []
(and @ready-to-lock? (not @record-button-is-animating?)) (cond
(do (and @ready-to-lock? (not @record-button-is-animating?))
(reset! locked? true) (do
(reset! ready-to-lock? false)) (reset! locked? true)
(and (not @locked?) (not @reviewing-audio?)) (reset! ready-to-lock? false))
(audio/stop-recording (and (not @locked?) (not @reviewing-audio?))
@recorder-ref (audio/stop-recording
(fn [] @recorder-ref
(cond (fn []
@ready-to-send? (cond
(when on-send @ready-to-send?
(on-send (audio/get-recorder-file-path @recorder-ref))) (when on-send
@ready-to-delete? (on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
(when on-cancel :duration @recording-length-ms}))
(on-cancel))) @ready-to-delete?
(reload-recorder-fn) (when on-cancel
(reset! recording? false) (on-cancel)))
(reset! ready-to-send? false) (reload-recorder-fn)
(reset! ready-to-delete? false) (reset! recording? false)
(reset! ready-to-lock? false) (reset! ready-to-send? false)
(reset! idle? true) (reset! ready-to-delete? false)
(js/setTimeout #(reset! idle? false) 1000) (reset! ready-to-lock? false)
(js/clearInterval @recording-timer) (reset! idle? true)
(reset! recording-length-ms 0) (js/setTimeout #(reset! idle? false) 1000)
(log/debug "[record-audio] stop recording - success")) (js/clearInterval @recording-timer)
#(log/error "[record-audio] stop recording - error: " %)))) (reset! recording-length-ms 0)
start-animation (fn [] (log/debug "[record-audio] stop recording - success"))
(set-value opacity 1) #(log/error "[record-audio] stop recording - error: " %))))
(animate-linear scale 2.6 signal-anim-duration) start-animation (fn []
;; TODO: Research if we can implement this with withSequence method (helpers/set-value opacity 1)
;; from Reanimated 2 (helpers/animate-linear scale 2.6 signal-anim-duration)
;; GitHub issue [#14561]: ;; TODO: Research if we can implement this with withSequence method
;; https://github.com/status-im/status-mobile/issues/14561 ;; from Reanimated 2
(reset! clear-timeout ;; GitHub issue [#14561]:
(js/setTimeout ;; https://github.com/status-im/status-mobile/issues/14561
(fn [] (reset! clear-timeout
(set-value scale scale-to-each) (js/setTimeout
(animate-linear-with-delay-loop scale (fn []
scale-to-total (helpers/set-value scale scale-to-each)
signal-anim-duration-2 (helpers/animate-linear-with-delay-loop scale
0)) scale-to-total
signal-anim-duration))) signal-anim-duration-2
stop-animation (fn [] 0))
(set-value opacity 0) signal-anim-duration)))
(reanimated/cancel-animation scale) stop-animation (fn []
(set-value scale 1) (helpers/set-value opacity 0)
(when @clear-timeout (js/clearTimeout @clear-timeout))) (reanimated/cancel-animation scale)
start-y-animation (fn [] (helpers/set-value scale 1)
(reset! record-button-at-initial-position? false) (when @clear-timeout (js/clearTimeout @clear-timeout)))
(reset! record-button-is-animating? true) start-y-animation (fn []
(animate-easing translate-y -64 250) (reset! record-button-at-initial-position? false)
(animate-linear-with-delay icon-opacity 0 33.33 76.66) (reset! record-button-is-animating? true)
(js/setTimeout (fn [] (helpers/animate-easing translate-y -64 250)
(reset! record-button-is-animating? false) (helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(when-not @touch-active? (complete-animation))) (js/setTimeout (fn []
250)) (reset! record-button-is-animating? false)
reset-y-animation (fn [] (when-not @touch-active? (complete-animation)))
(animate-easing translate-y 0 300) 250))
(animate-linear icon-opacity 1 500) reset-y-animation (fn []
(js/setTimeout (fn [] (helpers/animate-easing translate-y 0 300)
(reset! record-button-at-initial-position? true)) (helpers/animate-linear icon-opacity 1 500)
500)) (js/setTimeout (fn []
start-x-animation (fn [] (reset! record-button-at-initial-position? true))
(reset! record-button-at-initial-position? false) 500))
(reset! record-button-is-animating? true) start-x-animation (fn []
(animate-easing translate-x -64 250) (reset! record-button-at-initial-position? false)
(animate-linear-with-delay icon-opacity 0 33.33 76.66) (reset! record-button-is-animating? true)
(animate-linear red-overlay-opacity 1 33.33) (helpers/animate-easing translate-x -64 250)
(js/setTimeout (fn [] (helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(reset! record-button-is-animating? false) (helpers/animate-linear red-overlay-opacity 1 33.33)
(when-not @touch-active? (complete-animation))) (js/setTimeout (fn []
250)) (reset! record-button-is-animating? false)
reset-x-animation (fn [] (when-not @touch-active? (complete-animation)))
(animate-easing translate-x 0 300) 250))
(animate-linear icon-opacity 1 500) reset-x-animation (fn []
(animate-linear red-overlay-opacity 0 100) (helpers/animate-easing translate-x 0 300)
(js/setTimeout (fn [] (helpers/animate-linear icon-opacity 1 500)
(reset! record-button-at-initial-position? true)) (helpers/animate-linear red-overlay-opacity 0 100)
500)) (js/setTimeout (fn []
start-x-y-animation (fn [] (reset! record-button-at-initial-position? true))
(reset! record-button-at-initial-position? false) 500))
(reset! record-button-is-animating? true) start-x-y-animation (fn []
(animate-easing translate-y -44 200) (reset! record-button-at-initial-position? false)
(animate-easing translate-x -44 200) (reset! record-button-is-animating? true)
(animate-linear-with-delay icon-opacity 0 33.33 33.33) (helpers/animate-easing translate-y -44 200)
(animate-linear gray-overlay-opacity 1 33.33) (helpers/animate-easing translate-x -44 200)
(js/setTimeout (fn [] (helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
(reset! record-button-is-animating? false) (helpers/animate-linear gray-overlay-opacity 1 33.33)
(when-not @touch-active? (complete-animation))) (js/setTimeout (fn []
200)) (reset! record-button-is-animating? false)
reset-x-y-animation (fn [] (when-not @touch-active? (complete-animation)))
(animate-easing translate-y 0 300) 200))
(animate-easing translate-x 0 300) reset-x-y-animation (fn []
(animate-linear icon-opacity 1 500) (helpers/animate-easing translate-y 0 300)
(animate-linear gray-overlay-opacity 0 800) (helpers/animate-easing translate-x 0 300)
(js/setTimeout (fn [] (helpers/animate-linear icon-opacity 1 500)
(reset! record-button-at-initial-position? true)) (helpers/animate-linear gray-overlay-opacity 0 800)
800))] (js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800))]
(use-effect (fn [] (use-effect (fn []
(cond (cond
@recording? @recording?
@@ -4,60 +4,63 @@
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated] [react-native.reanimated :as reanimated]
[react-native.core :refer [use-effect]] [react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :refer [quo2.components.record-audio.record-audio.helpers :as helpers]))
[animate-linear
animate-linear-with-delay
animate-easing-with-delay
set-value]]))
(defn send-button (defn send-button
[recording? ready-to-send? reviewing-audio?] [recording? ready-to-send? reviewing-audio? force-show-controls?]
[:f> [:f>
(fn [] (fn []
(let [opacity (reanimated/use-shared-value 0) (let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-y (reanimated/use-shared-value 20) translate-y (reanimated/use-shared-value (if force-show-controls? 76 20))
connector-opacity (reanimated/use-shared-value 0) connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 12) width (reanimated/use-shared-value 12)
height (reanimated/use-shared-value 24) height (reanimated/use-shared-value 24)
border-radius-first-half (reanimated/use-shared-value 16) border-radius-first-half (reanimated/use-shared-value 16)
border-radius-second-half (reanimated/use-shared-value 8) border-radius-second-half (reanimated/use-shared-value 8)
start-y-animation (fn [] start-y-animation (fn []
(animate-linear-with-delay translate-y 12 50 133.33) (helpers/animate-linear-with-delay translate-y 12 50 133.33)
(animate-easing-with-delay connector-opacity 1 0 93.33) (helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(animate-easing-with-delay width 56 83.33 80) (helpers/animate-easing-with-delay width 56 83.33 80)
(animate-easing-with-delay height 56 83.33 80) (helpers/animate-easing-with-delay height 56 83.33 80)
(animate-easing-with-delay border-radius-first-half 28 83.33 80) (helpers/animate-easing-with-delay border-radius-first-half
(animate-easing-with-delay border-radius-second-half 28 83.33 80)) 28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-y-animation (fn [] reset-y-animation (fn []
(animate-linear translate-y 0 100) (helpers/animate-linear translate-y 0 100)
(set-value connector-opacity 0) (helpers/set-value connector-opacity 0)
(set-value width 12) (helpers/set-value width 12)
(set-value height 24) (helpers/set-value height 24)
(set-value border-radius-first-half 16) (helpers/set-value border-radius-first-half 16)
(set-value border-radius-second-half 8)) (helpers/set-value border-radius-second-half 8))
fade-in-animation (fn [] fade-in-animation (fn []
(animate-linear translate-y 0 200) (helpers/animate-linear translate-y 0 200)
(animate-linear opacity 1 200)) (helpers/animate-linear opacity 1 200))
fade-out-animation (fn [] fade-out-animation (fn []
(animate-linear (when-not force-show-controls?
translate-y (helpers/animate-linear
(if @reviewing-audio? 76 20) translate-y
200) (if @reviewing-audio? 76 20)
200))
(when-not @reviewing-audio? (when-not @reviewing-audio?
(animate-linear opacity 0 200)) (helpers/animate-linear opacity 0 200))
(set-value connector-opacity 0) (helpers/set-value connector-opacity 0)
(set-value width 24) (helpers/set-value width 24)
(set-value height 12) (helpers/set-value height 12)
(set-value border-radius-first-half 8) (helpers/set-value border-radius-first-half 8)
(set-value border-radius-second-half 16)) (helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn [] fade-out-reset-animation (fn []
(animate-linear opacity 0 200) (helpers/animate-linear opacity 0 200)
(animate-linear-with-delay translate-y 20 0 200) (helpers/animate-linear-with-delay translate-y 20 0 200)
(set-value connector-opacity 0) (helpers/set-value connector-opacity 0)
(set-value width 24) (helpers/set-value width 24)
(set-value height 12) (helpers/set-value height 12)
(set-value border-radius-first-half 8) (helpers/set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))] (helpers/set-value border-radius-second-half 16))]
(use-effect (fn [] (use-effect (fn []
(if @recording? (if @recording?
(fade-in-animation) (fade-in-animation)
@@ -14,19 +14,17 @@
[quo2.components.record-audio.record-audio.buttons.send-button :as send-button] [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.lock-button :as lock-button]
[quo2.components.record-audio.record-audio.buttons.delete-button :as delete-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])) [quo2.components.record-audio.record-audio.buttons.record-button :as record-button]
[react-native.platform :as platform]
[clojure.string :as string]))
(def ^:private min-audio-duration-ms 500) (def ^:private min-audio-duration-ms 500)
(def ^:private max-audio-duration-ms 120000) (def ^:private max-audio-duration-ms 120000)
(def ^:private metering-interval 100) (def ^:private metering-interval 100)
(def ^:private base-filename "am.") (def ^:private base-filename "am")
(def ^:private default-format "aac") (def ^:private default-format ".aac")
(def ^:private rec-options (def min-touch-duration 150)
(merge
audio/default-recorder-options
{:filename (str base-filename default-format)
:meteringInterval metering-interval}))
(def ^:private record-button-area-big (def ^:private record-button-area-big
{:width 56 {:width 56
@@ -149,8 +147,10 @@
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]))]) {:color (colors/theme-colors colors/neutral-100 colors/white)}]]))])
(defn view (defn view
[{:keys [on-start-recording on-send on-cancel on-reviewing-audio record-audio-permission-granted [{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio
on-request-record-audio-permission on-check-audio-permissions]}] record-audio-permission-granted
on-request-record-audio-permission on-check-audio-permissions
audio-file]}]
[:f> [:f>
(fn [] (fn []
(let [recording? (reagent/atom false) (let [recording? (reagent/atom false)
@@ -163,30 +163,39 @@
recording-length-ms (reagent/atom 0) recording-length-ms (reagent/atom 0)
audio-current-time-ms (reagent/atom 0) audio-current-time-ms (reagent/atom 0)
seeking-audio? (reagent/atom false) seeking-audio? (reagent/atom false)
force-show-controls? (reagent/atom false)
clear-timeout (atom nil) clear-timeout (atom nil)
record-button-at-initial-position? (atom true) record-button-at-initial-position? (atom true)
record-button-is-animating? (atom false) record-button-is-animating? (atom false)
idle? (atom false) idle? (atom false)
recorder-ref (atom nil)
player-ref (atom nil)
touch-active? (atom false) touch-active? (atom false)
recording-timer (atom nil) recording-timer (atom nil)
playing-timer (atom nil) playing-timer (atom nil)
recorder-ref (atom nil)
player-ref (atom nil)
output-file (atom nil) output-file (atom nil)
reached-max-duration? (atom false) 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 destroy-player
(fn [] (fn []
(audio/destroy-player @player-ref) (audio/destroy-player @player-ref)
(reset! player-ref nil)) (reset! player-ref nil))
reload-player reload-player
(fn [] (fn [audio-file]
(when @player-ref (when @player-ref
(destroy-player)) (destroy-player))
(reset! player-ref (reset! player-ref
(audio/new-player (audio/new-player
(:filename rec-options) (or audio-file (:filename rec-options))
{:autoDestroy false {:autoDestroy false
:continuesToPlayInBackground false} :continuesToPlayInBackground false
:category audio/PLAYBACK}
(fn [] (fn []
(reset! playing-audio? false) (reset! playing-audio? false)
(when @playing-timer (when @playing-timer
@@ -210,9 +219,37 @@
rec-options rec-options
#(log/debug "[record-audio] new recorder - on meter") #(log/debug "[record-audio] new recorder - on meter")
#(log/debug "[record-audio] new recorder - on ended")))) #(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 on-start-should-set-responder
(fn [^js e] (fn [^js e]
(when-not (or @locked? @idle? (nil? e)) (when-not (or @locked? @idle? (nil? e) @disabled?)
(let [pressed-record-button? (touch-inside-area? (let [pressed-record-button? (touch-inside-area?
{:location-x (oops/oget e "nativeEvent.locationX") {:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY") :location-y (oops/oget e "nativeEvent.locationY")
@@ -220,7 +257,12 @@
:ignore-max-y? false :ignore-max-y? false
:ignore-min-x? false :ignore-min-x? false
:ignore-max-x? false} :ignore-max-x? false}
record-button-area)] 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"))
(when-not @reviewing-audio? (when-not @reviewing-audio?
(if record-audio-permission-granted (if record-audio-permission-granted
(do (do
@@ -231,8 +273,9 @@
(when @recording-timer (when @recording-timer
(js/clearInterval @recording-timer)) (js/clearInterval @recording-timer))
(reset! output-file nil) (reset! output-file nil)
(reset! recorder-ref new-recorder)
(audio/start-recording (audio/start-recording
@recorder-ref new-recorder
(fn [] (fn []
(reset! audio-current-time-ms 0) (reset! audio-current-time-ms 0)
(reset! recording-timer (reset! recording-timer
@@ -251,28 +294,29 @@
(reset! ready-to-send? false) (reset! ready-to-send? false)
(reset! ready-to-delete? false) (reset! ready-to-delete? false)
(audio/stop-recording (audio/stop-recording
@recorder-ref new-recorder
(fn [] (fn []
(reset! output-file (audio/get-recorder-file-path (reset! output-file (audio/get-recorder-file-path
@recorder-ref)) new-recorder))
(reload-recorder) (reload-player nil)
(reload-player)
(log/debug "[record-audio] stop recording - success")) (log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %)) #(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000) (js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer) (js/clearInterval @recording-timer)
(reset! recording-length-ms 0) (reset! recording-length-ms 0)
(when on-reviewing-audio (when on-reviewing-audio
(on-reviewing-audio))))) (on-reviewing-audio (audio/get-recorder-file-path
new-recorder))))))
metering-interval)) metering-interval))
(log/debug "[record-audio] start recording - success")) (log/debug "[record-audio] start recording - success"))
#(log/error "[record-audio] start recording - error: " %)) #(log/error "[record-audio] start recording - error: " %))
(when on-start-recording (when on-start-recording
(on-start-recording)))) (on-start-recording))))
(some-> on-request-record-audio-permission))) (when on-request-record-audio-permission
(on-request-record-audio-permission))))
(when record-audio-permission-granted (when record-audio-permission-granted
(reset! touch-active? true)))) (reset! touch-active? true))))
(not @idle?)) (and (not @idle?) (not @disabled?)))
on-responder-move on-responder-move
(fn [^js e] (fn [^js e]
(when-not @locked? (when-not @locked?
@@ -349,43 +393,49 @@
(when (and (when (and
(not @idle?) (not @idle?)
(not @reached-max-duration?)) (not @reached-max-duration?))
(let [touch-area {:location-x (oops/oget e "nativeEvent.locationX") (let [touch-area {:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY") :location-y (oops/oget e "nativeEvent.locationY")
:ignore-min-y? false :ignore-min-y? false
:ignore-max-y? false :ignore-max-y? false
:ignore-min-x? false :ignore-min-x? false
:ignore-max-x? false} :ignore-max-x? false}
on-record-button? (touch-inside-area? on-record-button? (touch-inside-area?
touch-area touch-area
record-button-area-big) record-button-area-big)
on-send-button? (touch-inside-area? on-send-button? (touch-inside-area?
touch-area touch-area
(send-button-area (send-button-area
{:active? false {:active? false
:reviewing-audio? true})) :reviewing-audio? true}))
on-delete-button? (touch-inside-area? on-delete-button? (touch-inside-area?
touch-area touch-area
(delete-button-area (delete-button-area
{:active? false {:active? false
:reviewing-audio? true}))] :reviewing-audio? true}))
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
audio-length @recording-length-ms]
(cond (cond
(and @reviewing-audio? on-send-button?) (and @reviewing-audio? on-send-button?)
(do (do
(reset! reviewing-audio? false) (reset! reviewing-audio? false)
(reset! audio-current-time-ms 0) (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 (when @player-ref
(audio/stop-playing (audio/stop-playing
@player-ref @player-ref
(fn [] (fn []
(destroy-player) (destroy-player)
(log/debug "[record-audio] stop playing - success")) (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?) (and @reviewing-audio? on-delete-button?)
(do (do
(reset! reviewing-audio? false) (reset! reviewing-audio? false)
(reset! audio-current-time-ms 0) (reset! audio-current-time-ms 0)
(reset! force-show-controls? false)
(destroy-player) (destroy-player)
(when on-cancel (when on-cancel
(on-cancel))) (on-cancel)))
@@ -393,61 +443,86 @@
(do (do
(reset! locked? true) (reset! locked? true)
(reset! ready-to-lock? false)) (reset! ready-to-lock? false))
(and (not @reviewing-audio?) on-record-button?) (and (not @reviewing-audio?)
(or on-record-button?
(and (not @ready-to-delete?)
(not @ready-to-lock?)
(not @ready-to-send?))))
(do (do
(if (>= @recording-length-ms min-audio-duration-ms) (reset! disabled? (<= touch-timestamp-diff min-touch-duration))
(do (reset! reviewing-audio? true) (js/setTimeout
(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 [] (fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref)) (if (>= @recording-length-ms min-audio-duration-ms)
(reload-recorder) (do (reset! reviewing-audio? true)
(reload-player) (reset! idle? false)
(log/debug "[record-audio] stop recording - success")) (when on-reviewing-audio
#(log/error "[record-audio] stop recording - error: " %)) (on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
(js/setTimeout #(reset! idle? false) 1000) (do (when on-cancel
(js/clearInterval @recording-timer) (on-cancel))
(reset! recording-length-ms 0)) (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)))
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?)) (and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
(audio/stop-recording (do
@recorder-ref (reset! disabled? (<= touch-timestamp-diff min-touch-duration))
(fn [] (js/setTimeout
(cond (fn []
@ready-to-send? (audio/stop-recording
(when on-send @recorder-ref
(on-send (audio/get-recorder-file-path @recorder-ref))) (fn []
@ready-to-delete? (cond
(when on-cancel @ready-to-send?
(on-cancel))) (when on-send
(reload-recorder) (on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
(reset! recording? false) :duration @recording-length-ms}))
(reset! ready-to-send? false) @ready-to-delete?
(reset! ready-to-delete? false) (when on-cancel
(reset! ready-to-lock? false) (on-cancel)))
(reset! idle? true) (reset! recording? false)
(js/setTimeout #(reset! idle? false) 1000) (reset! ready-to-send? false)
(js/clearInterval @recording-timer) (reset! ready-to-delete? false)
(reset! recording-length-ms 0) (reset! ready-to-lock? false)
(log/debug "[record-audio] stop recording - success")) (reset! idle? true)
#(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)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %)))
(if (> touch-timestamp-diff min-touch-duration) 0 250)))))
(reset! touch-active? false)) (reset! touch-active? false))
(when @reached-max-duration? (when @reached-max-duration?
(reset! reached-max-duration? false)))] (reset! reached-max-duration? false))
(reset! touch-timestamp nil))]
(fn [] (fn []
(use-effect (fn [] (use-effect (fn []
(some-> on-check-audio-permissions) (when on-check-audio-permissions
(reload-recorder))) (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)))))
[rn/view [rn/view
{:style style/bar-container} {:style style/bar-container
:pointer-events :box-none}
(when @reviewing-audio? (when @reviewing-audio?
[:<> [:<>
[play-button playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio?] [play-button playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio?]
@@ -463,13 +538,18 @@
[rn/view [rn/view
{:test-ID "record-audio" {:test-ID "record-audio"
:style style/button-container :style style/button-container
:hit-slop {:top (if platform/ios? -70 0)
:bottom 0
:left 0
:right 0}
:pointer-events :box-only :pointer-events :box-only
:on-start-should-set-responder on-start-should-set-responder :on-start-should-set-responder on-start-should-set-responder
:on-responder-move on-responder-move :on-responder-move on-responder-move
:on-responder-release on-responder-release} :on-responder-release on-responder-release}
[delete-button/delete-button recording? ready-to-delete? reviewing-audio?] [delete-button/delete-button recording? ready-to-delete? reviewing-audio?
@force-show-controls?]
[lock-button/lock-button recording? ready-to-lock? locked?] [lock-button/lock-button recording? ready-to-lock? locked?]
[send-button/send-button recording? ready-to-send? reviewing-audio?] [send-button/send-button recording? ready-to-send? reviewing-audio? @force-show-controls?]
[record-button-big/record-button-big [record-button-big/record-button-big
recording? recording?
ready-to-send? ready-to-send?
@@ -6,9 +6,9 @@
[react-native.core :as rn])) [react-native.core :as rn]))
(defn card-background (defn card-background
[{:keys [custom-color]}] [{:keys [customization-color]}]
[:<> [:<>
[rn/view {:style (style/background-top custom-color)}] [rn/view {:style (style/background-top customization-color)}]
[rn/view {:style (style/background-bottom)}]]) [rn/view {:style (style/background-bottom)}]])
(defn avatar (defn avatar
@@ -28,12 +28,12 @@
:i/more]]) :i/more]])
(defn account (defn account
[{:keys [account-name account-address avatar-icon custom-color on-press-menu]}] [{:keys [account-name account-address avatar-icon customization-color on-press-menu]}]
[rn/view {:style style/card} [rn/view {:style style/card}
[card-background {:custom-color custom-color}] [card-background {:customization-color customization-color}]
[rn/view {:style style/card-top} [rn/view {:style style/card-top}
[avatar [avatar
{:color custom-color {:color customization-color
:icon avatar-icon}] :icon avatar-icon}]
[menu-button {:on-press on-press-menu}]] [menu-button {:on-press on-press-menu}]]
[rn/view {:style style/card-bottom} [rn/view {:style style/card-bottom}
@@ -0,0 +1,33 @@
(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))))
+49 -35
View File
@@ -27,15 +27,18 @@
background-color background-color
icon icon
text-color text-color
label]}] label
accessibility-label]}]
(let [paragraph-size (if (= size :small) :paragraph-2 :paragraph-1)] (let [paragraph-size (if (= size :small) :paragraph-2 :paragraph-1)]
[rn/view [rn/view
(assoc (if (= size :small) {:accessible true
small-container-style :accessibility-label accessibility-label
large-container-style) :style (assoc (if (= size :small)
:border-width 1 small-container-style
:border-color border-color large-container-style)
:background-color background-color) :border-width 1
:border-color border-color
:background-color background-color)}
[rn/view [rn/view
{:flex-direction :row {:flex-direction :row
:flex 1} :flex 1}
@@ -45,7 +48,7 @@
[icon/icon [icon/icon
icon icon
{:no-color true {:no-color true
:size 12}]] :size (if (= size :large) 20 12)}]]
[text/text [text/text
{:size paragraph-size {:size paragraph-size
:weight :medium :weight :medium
@@ -53,41 +56,51 @@
:color text-color}} label]]]))) :color text-color}} label]]])))
(defn- positive (defn- positive
[size theme label] [size theme label _]
[base-tag [base-tag
{:size size {:accessibility-label :status-tag-positive
:icon :verified :size size
:background-color colors/success-50-opa-10 :icon :i/positive-state
:border-color colors/success-50-opa-20 :background-color colors/success-50-opa-10
:label label :border-color colors/success-50-opa-20
:text-color (if (= theme :light) :label label
colors/success-50 ;; The positive tag uses the same color for `light` and `dark blur` variant
colors/success-60)}]) :text-color (if (= theme :dark) colors/success-60 colors/success-50)}])
(defn- negative (defn- negative
[size theme label] [size theme label _]
[base-tag [base-tag
{:size size {:accessibility-label :status-tag-negative
:icon :untrustworthy :size size
:background-color colors/danger-50-opa-10 :icon :i/negative-state
:border-color colors/danger-50-opa-20 :background-color colors/danger-50-opa-10
:label label :border-color colors/danger-50-opa-20
:text-color (if (= theme :light) :label label
colors/danger-50 ;; The negative tag uses the same color for `dark` and `dark blur` variant
colors/danger-60)}]) :text-color (if (= theme :light) colors/danger-50 colors/danger-60)}])
(defn- pending (defn- pending
[size _ label] [size theme label blur?]
[base-tag [base-tag
{:size size {:accessibility-label :status-tag-pending
:icon :pending :size size
:label label :label label
:background-color colors/white-opa-5 :icon (if blur?
:border-color colors/white-opa-5 :i/pending-dark-blur
:text-color colors/white-opa-70}]) (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))}])
(defn status-tag (defn status-tag
[{:keys [status size override-theme label]}] [{:keys [status size override-theme label blur?]}]
(when status (when status
(when-let [status-component (case (:type status) (when-let [status-component (case (:type status)
:positive positive :positive positive
@@ -97,4 +110,5 @@
[status-component [status-component
size size
(or override-theme (quo2.theme/get-theme)) (or override-theme (quo2.theme/get-theme))
label]))) label
blur?])))
+12 -4
View File
@@ -5,7 +5,7 @@
quo2.components.avatars.channel-avatar quo2.components.avatars.channel-avatar
quo2.components.avatars.group-avatar quo2.components.avatars.group-avatar
quo2.components.avatars.icon-avatar quo2.components.avatars.icon-avatar
quo2.components.avatars.user-avatar quo2.components.avatars.user-avatar.view
quo2.components.avatars.wallet-user-avatar quo2.components.avatars.wallet-user-avatar
quo2.components.banners.banner.view quo2.components.banners.banner.view
quo2.components.buttons.button quo2.components.buttons.button
@@ -30,7 +30,9 @@
quo2.components.icon quo2.components.icon
quo2.components.info.info-message quo2.components.info.info-message
quo2.components.info.information-box quo2.components.info.information-box
quo2.components.input.view quo2.components.inputs.input.view
quo2.components.inputs.title-input.view
quo2.components.inputs.profile-input.view
quo2.components.list-items.channel quo2.components.list-items.channel
quo2.components.list-items.menu-item quo2.components.list-items.menu-item
quo2.components.list-items.preview-list quo2.components.list-items.preview-list
@@ -50,6 +52,7 @@
quo2.components.profile.profile-card.view quo2.components.profile.profile-card.view
quo2.components.profile.select-profile.view quo2.components.profile.select-profile.view
quo2.components.reactions.reaction quo2.components.reactions.reaction
quo2.components.record-audio.record-audio.view
quo2.components.selectors.disclaimer.view quo2.components.selectors.disclaimer.view
quo2.components.selectors.filter.view quo2.components.selectors.filter.view
quo2.components.selectors.selectors quo2.components.selectors.selectors
@@ -102,7 +105,7 @@
(def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar) (def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar)
(def group-avatar quo2.components.avatars.group-avatar/group-avatar) (def group-avatar quo2.components.avatars.group-avatar/group-avatar)
(def icon-avatar quo2.components.avatars.icon-avatar/icon-avatar) (def icon-avatar quo2.components.avatars.icon-avatar/icon-avatar)
(def user-avatar quo2.components.avatars.user-avatar/user-avatar) (def user-avatar quo2.components.avatars.user-avatar.view/user-avatar)
(def wallet-user-avatar quo2.components.avatars.wallet-user-avatar/wallet-user-avatar) (def wallet-user-avatar quo2.components.avatars.wallet-user-avatar/wallet-user-avatar)
;;;; BANNER ;;;; BANNER
@@ -141,7 +144,9 @@
(def permission-context quo2.components.drawers.permission-context.view/view) (def permission-context quo2.components.drawers.permission-context.view/view)
;;;; INPUTS ;;;; INPUTS
(def input quo2.components.input.view/input) (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)
;;;; LIST ITEMS ;;;; LIST ITEMS
(def channel-list-item quo2.components.list-items.channel/list-item) (def channel-list-item quo2.components.list-items.channel/list-item)
@@ -162,6 +167,9 @@
(def profile-card quo2.components.profile.profile-card.view/profile-card) (def profile-card quo2.components.profile.profile-card.view/profile-card)
(def select-profile quo2.components.profile.select-profile.view/view) (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 ;;;; SETTINGS
(def privacy-option quo2.components.settings.privacy-option/card) (def privacy-option quo2.components.settings.privacy-option/card)
(def account quo2.components.settings.accounts.view/account) (def account quo2.components.settings.accounts.view/account)
+6 -2
View File
@@ -1,5 +1,6 @@
(ns quo2.core-spec (ns quo2.core-spec
(:require [quo2.components.banners.banner.component-spec] (:require [quo2.components.avatars.user-avatar.component-spec]
[quo2.components.banners.banner.component-spec]
[quo2.components.buttons.--tests--.buttons-component-spec] [quo2.components.buttons.--tests--.buttons-component-spec]
[quo2.components.counter.--tests--.counter-component-spec] [quo2.components.counter.--tests--.counter-component-spec]
[quo2.components.dividers.--tests--.divider-label-component-spec] [quo2.components.dividers.--tests--.divider-label-component-spec]
@@ -8,6 +9,8 @@
[quo2.components.drawers.drawer-buttons.component-spec] [quo2.components.drawers.drawer-buttons.component-spec]
[quo2.components.drawers.permission-context.component-spec] [quo2.components.drawers.permission-context.component-spec]
[quo2.components.colors.color-picker.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.markdown.--tests--.text-component-spec]
[quo2.components.onboarding.small-option-card.component-spec] [quo2.components.onboarding.small-option-card.component-spec]
[quo2.components.password.tips.component-spec] [quo2.components.password.tips.component-spec]
@@ -15,4 +18,5 @@
[quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec] [quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec]
[quo2.components.profile.select-profile.component-spec] [quo2.components.profile.select-profile.component-spec]
[quo2.components.selectors.--tests--.selectors-component-spec] [quo2.components.selectors.--tests--.selectors-component-spec]
[quo2.components.selectors.filter.component-spec])) [quo2.components.selectors.filter.component-spec]
[quo2.components.tags.--tests--.status-tags-component-spec]))
+8 -4
View File
@@ -72,6 +72,9 @@
(def neutral-80-opa-90 (alpha neutral-80 0.9)) (def neutral-80-opa-90 (alpha neutral-80 0.9))
(def neutral-80-opa-95 (alpha neutral-80 0.95)) (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 ;;95 with transparency
(def neutral-95-opa-60 (alpha neutral-95 0.6)) (def neutral-95-opa-60 (alpha neutral-95 0.6))
(def neutral-95-opa-70 (alpha neutral-95 0.7)) (def neutral-95-opa-70 (alpha neutral-95 0.7))
@@ -98,6 +101,7 @@
(def white "#ffffff") (def white "#ffffff")
;; with transparency ;; with transparency
(def white-opa-0 (alpha white 0))
(def white-opa-5 (alpha white 0.05)) (def white-opa-5 (alpha white 0.05))
(def white-opa-10 (alpha white 0.1)) (def white-opa-10 (alpha white 0.1))
(def white-opa-20 (alpha white 0.2)) (def white-opa-20 (alpha white 0.2))
@@ -139,8 +143,8 @@
;;;;Success ;;;;Success
;;Solid ;;Solid
(def success-50 "#26A69A") (def success-50 "#23ADA0")
(def success-60 "#208B81") (def success-60 "#1C8A80")
;;50 with transparency ;;50 with transparency
(def success-50-opa-5 (alpha success-50 0.05)) (def success-50-opa-5 (alpha success-50 0.05))
@@ -162,8 +166,8 @@
(def danger-opa-40 (alpha danger 0.4)) (def danger-opa-40 (alpha danger 0.4))
;;Solid ;;Solid
(def danger-50 "#E65F5C") (def danger-50 "#E95460")
(def danger-60 "#C1504D") (def danger-60 "#BA434D")
;;50 with transparency ;;50 with transparency
(def danger-50-opa-5 (alpha danger-50 0.05)) (def danger-50-opa-5 (alpha danger-50 0.05))
+7 -2
View File
@@ -1,5 +1,6 @@
(ns react-native.audio-toolkit (ns react-native.audio-toolkit
(:require ["@react-native-community/audio-toolkit" :refer (Player Recorder MediaStates)])) (:require ["@react-native-community/audio-toolkit" :refer
(Player Recorder MediaStates PlaybackCategories)]))
;; get mediastates from react module ;; get mediastates from react module
(def PLAYING (.-PLAYING ^js MediaStates)) (def PLAYING (.-PLAYING ^js MediaStates))
@@ -11,13 +12,17 @@
(def DESTROYED (.-DESTROYED ^js MediaStates)) (def DESTROYED (.-DESTROYED ^js MediaStates))
(def SEEKING (.-SEEKING ^js MediaStates)) (def SEEKING (.-SEEKING ^js MediaStates))
;; get PlaybackCategories from react module
(def PLAYBACK (.-Playback ^js PlaybackCategories))
(def default-recorder-options (def default-recorder-options
{:filename "recording.aac" {:filename "recording.aac"
:bitrate 32000 :bitrate 32000
:channels 1 :channels 1
:sampleRate 22050 :sampleRate 22050
:quality "medium" ; ios only :quality "medium" ; ios only
:meteringInterval 50}) :meteringInterval 50
:category PLAYBACK})
(defn get-state (defn get-state
[player-recorder] [player-recorder]
+4
View File
@@ -98,6 +98,10 @@
(def use-ref react/useRef) (def use-ref react/useRef)
(defn current-ref
[ref]
(oops/oget ref "current"))
(defn use-effect (defn use-effect
([effect-fn] ([effect-fn]
(use-effect effect-fn [])) (use-effect effect-fn []))
+23 -14
View File
@@ -1,11 +1,13 @@
(ns react-native.gesture (ns react-native.gesture
(:require ["react-native-gesture-handler" :refer (:require ["react-native-gesture-handler" :refer
(Gesture (Gesture
GestureDetector GestureDetector
RectButton RectButton
Swipeable Swipeable
TouchableWithoutFeedback TouchableWithoutFeedback
gestureHandlerRootHOC)] gestureHandlerRootHOC
FlatList)]
[react-native.flat-list :as rn-flat-list]
[reagent.core :as reagent])) [reagent.core :as reagent]))
(def gesture-detector (reagent/adapt-react-class GestureDetector)) (def gesture-detector (reagent/adapt-react-class GestureDetector))
@@ -17,7 +19,7 @@
(defn gesture-pan [] (.Pan ^js Gesture)) (defn gesture-pan [] (.Pan ^js Gesture))
(defn gesture-pinch [] (.Pinch ^js Gesture)) (defn gesture-pinch [] (.Pinch ^js Gesture))
(defn on-touches-down [gesture handler] (.onTouchesDown ^js gesture handler))
(defn on-begin [gesture handler] (.onBegin ^js gesture handler)) (defn on-begin [gesture handler] (.onBegin ^js gesture handler))
(defn on-start [gesture handler] (.onStart ^js gesture handler)) (defn on-start [gesture handler] (.onStart ^js gesture handler))
@@ -28,6 +30,8 @@
(defn on-finalize [gesture handler] (.onFinalize ^js gesture handler)) (defn on-finalize [gesture handler] (.onFinalize ^js gesture handler))
(defn max-pointers [gesture count] (.maxPointers ^js gesture count))
(defn number-of-taps [gesture count] (.numberOfTaps ^js gesture count)) (defn number-of-taps [gesture count] (.numberOfTaps ^js gesture count))
(defn enabled [gesture enabled?] (.enabled ^js gesture enabled?)) (defn enabled [gesture enabled?] (.enabled ^js gesture enabled?))
@@ -53,13 +57,18 @@
[{:keys [render-left-actions render-right-actions] :as props} & children] [{:keys [render-left-actions render-right-actions] :as props} & children]
(into [swipeable-component (into [swipeable-component
(cond-> props (cond-> props
render-left-actions render-left-actions
(assoc :render-left-actions (assoc :render-left-actions
(fn [& args] (fn [& args]
(reagent/as-element (apply render-left-actions args)))) (reagent/as-element (apply render-left-actions args))))
render-right-actions render-right-actions
(assoc :render-right-actions (assoc :render-right-actions
(fn [& args] (fn [& args]
(reagent/as-element (apply render-right-actions args)))))] (reagent/as-element (apply render-right-actions args)))))]
children)) children))
(def gesture-flat-list (reagent/adapt-react-class FlatList))
(defn flat-list
[props]
[gesture-flat-list (rn-flat-list/base-list-props props)])
+12
View File
@@ -73,3 +73,15 @@
(defn merge-options (defn merge-options
[id opts] [id opts]
(.mergeOptions Navigation id (clj->js opts))) (.mergeOptions Navigation id (clj->js opts)))
(def constants (atom nil))
(defn status-bar-height
[]
(:status-bar-height @constants))
(.then (.constants Navigation)
(fn [^js consts]
(reset! constants {:top-bar-height (.-topBarHeight consts)
:bottom-tabs-height (.-bottomTabsHeight consts)
:status-bar-height (.-statusBarHeight consts)})))
+16 -12
View File
@@ -5,6 +5,7 @@
["@react-native-community/blur" :as blur] ["@react-native-community/blur" :as blur]
["react-native-reanimated" :default reanimated :refer ["react-native-reanimated" :default reanimated :refer
(useSharedValue useAnimatedStyle (useSharedValue useAnimatedStyle
useAnimatedGestureHandler
withTiming withTiming
withDelay withDelay
withSpring withSpring
@@ -18,7 +19,8 @@
LinearTransition)] LinearTransition)]
[reagent.core :as reagent] [reagent.core :as reagent]
[react-native.flat-list :as rn-flat-list] [react-native.flat-list :as rn-flat-list]
[utils.collection])) [utils.collection]
[utils.worklets.core :as worklets.core]))
;; Animations ;; Animations
(def slide-in-up-animation SlideInUp) (def slide-in-up-animation SlideInUp)
@@ -29,14 +31,18 @@
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated))) (def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
(def view (reagent/adapt-react-class (.-View reanimated))) (def view (reagent/adapt-react-class (.-View reanimated)))
(def scroll-view (reagent/adapt-react-class (.-ScrollView reanimated)))
(def image (reagent/adapt-react-class (.-Image reanimated))) (def image (reagent/adapt-react-class (.-Image reanimated)))
;; TODO: This one should use FlatList from Reanimated.
;; Trying to use Flatlist from RA causes test to fail: "The first argument must be a component. Instead
;; received: object"
(def reanimated-flat-list (reagent/adapt-react-class (.-FlatList ^js rn))) (def reanimated-flat-list (reagent/adapt-react-class (.-FlatList ^js rn)))
(defn flat-list (defn flat-list
[props] [props]
[reanimated-flat-list (rn-flat-list/base-list-props props)]) [reanimated-flat-list (rn-flat-list/base-list-props props)])
(def touchable-opacity (create-animated-component (.-TouchableOpacity ^js rn))) (def touchable-opacity (create-animated-component (.-TouchableOpacity ^js rn)))
(def linear-gradient (create-animated-component LinearGradient)) (def linear-gradient (create-animated-component LinearGradient))
(def fast-image (create-animated-component FastImage)) (def fast-image (create-animated-component FastImage))
(def blur-view (create-animated-component (.-BlurView blur))) (def blur-view (create-animated-component (.-BlurView blur)))
@@ -60,6 +66,8 @@
(def in-out (def in-out
(.-inOut ^js Easing)) (.-inOut ^js Easing))
(defn default-easing [] (in-out (.-quad ^js Easing)))
(def easings (def easings
{:linear (bezier 0 0 1 1) {:linear (bezier 0 0 1 1)
:easing1 (bezier 0.25 0.1 0.25 1) :easing1 (bezier 0.25 0.1 0.25 1)
@@ -77,24 +85,20 @@
(when (and anim (some? val)) (when (and anim (some? val))
(set! (.-value anim) val))) (set! (.-value anim) val)))
;; Worklets
(def worklet-factory (js/require "../src/js/worklet_factory.js"))
(defn interpolate (defn interpolate
([shared-value input-range output-range] ([shared-value input-range output-range]
(interpolate shared-value input-range output-range nil)) (interpolate shared-value input-range output-range nil))
([shared-value input-range output-range extrapolation] ([shared-value input-range output-range extrapolation]
(.interpolateValue ^js worklet-factory (worklets.core/interpolate-value
shared-value shared-value
(clj->js input-range) (clj->js input-range)
(clj->js output-range) (clj->js output-range)
(clj->js extrapolation)))) (clj->js extrapolation))))
;;;; Component Animations
(defn apply-animations-to-style (defn apply-animations-to-style
[animations style] [animations style]
(use-animated-style (use-animated-style
(.applyAnimationsToStyle ^js worklet-factory (clj->js animations) (clj->js style)))) (worklets.core/apply-animations-to-style (clj->js animations) (clj->js style))))
;; Animators ;; Animators
(defn animate-shared-value-with-timing (defn animate-shared-value-with-timing
+9 -1
View File
@@ -1,6 +1,6 @@
(ns react-native.safe-area (ns react-native.safe-area
(:require ["react-native-safe-area-context" :as safe-area-context :refer (:require ["react-native-safe-area-context" :as safe-area-context :refer
(SafeAreaProvider SafeAreaInsetsContext)] (SafeAreaProvider SafeAreaInsetsContext useSafeAreaInsets)]
[reagent.core :as reagent])) [reagent.core :as reagent]))
(def ^:private consumer-raw (reagent/adapt-react-class (.-Consumer ^js SafeAreaInsetsContext))) (def ^:private consumer-raw (reagent/adapt-react-class (.-Consumer ^js SafeAreaInsetsContext)))
@@ -13,3 +13,11 @@
(fn [^js insets] (fn [^js insets]
(reagent/as-element (reagent/as-element
[component (js->clj insets :keywordize-keys true)]))]) [component (js->clj insets :keywordize-keys true)]))])
(defn use-safe-area
[]
(let [insets ^js (useSafeAreaInsets)]
{:top (.-top insets)
:bottom (.-bottom insets)
:left (.-left insets)
:right (.-right insets)}))
+16 -10
View File
@@ -195,15 +195,21 @@
(chat.message/send-messages messages))))) (chat.message/send-messages messages)))))
(rf/defn send-audio-message (rf/defn send-audio-message
[cofx audio-path duration current-chat-id] [{:keys [db] :as cofx} audio-path duration current-chat-id]
(when-not (string/blank? audio-path) (let [{:keys [message-id]}
(chat.message/send-message (get-in db [:chat/inputs current-chat-id :metadata :responding-to-message])]
cofx (when-not (string/blank? audio-path)
{:chat-id current-chat-id (rf/merge
:content-type constants/content-type-audio {:db (assoc-in db [:chat/inputs current-chat-id :metadata :responding-to-message] nil)}
:audio-path audio-path (chat.message/send-message
:audio-duration-ms duration (merge
:text (i18n/label :t/update-to-listen-audio {"locale" "en"})}))) {:chat-id current-chat-id
:content-type constants/content-type-audio
:audio-path audio-path
:audio-duration-ms duration
:text (i18n/label :t/update-to-listen-audio {"locale" "en"})}
(when message-id
{:response-to message-id})))))))
(rf/defn send-sticker-message (rf/defn send-sticker-message
[cofx {:keys [hash packID pack]} current-chat-id] [cofx {:keys [hash packID pack]} current-chat-id]
@@ -293,5 +299,5 @@
(rf/defn chat-send-audio (rf/defn chat-send-audio
{:events [:chat/send-audio]} {:events [:chat/send-audio]}
[{{:keys [current-chat-id]} :db :as cofx} audio-path duration] [{{:keys [current-chat-id] :as db} :db :as cofx} audio-path duration]
(send-audio-message cofx audio-path duration current-chat-id)) (send-audio-message cofx audio-path duration current-chat-id))
+94 -18
View File
@@ -4,6 +4,7 @@
[clojure.walk :as walk] [clojure.walk :as walk]
[quo.design-system.colors :as colors] [quo.design-system.colors :as colors]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.utils.types :as types]
[status-im.async-storage.core :as async-storage] [status-im.async-storage.core :as async-storage]
[status-im.ui.components.emoji-thumbnail.styles :as emoji-thumbnail-styles] [status-im.ui.components.emoji-thumbnail.styles :as emoji-thumbnail-styles]
[status-im.utils.universal-links.core :as universal-links] [status-im.utils.universal-links.core :as universal-links]
@@ -14,7 +15,8 @@
[status-im2.navigation.events :as navigation] [status-im2.navigation.events :as navigation]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im2.contexts.chat.events :as chat.events]))
(def crop-size 1000) (def crop-size 1000)
@@ -36,9 +38,9 @@
:chatId :chat-id})) :chatId :chat-id}))
(defn <-requests-to-join-community-rpc (defn <-requests-to-join-community-rpc
[requests] [requests key-fn]
(reduce (fn [acc r] (reduce (fn [acc r]
(assoc acc (:id r) (<-request-to-join-community-rpc r))) (assoc acc (key-fn r) (<-request-to-join-community-rpc r)))
{} {}
requests)) requests))
@@ -80,14 +82,28 @@
(update :chats <-chats-rpc) (update :chats <-chats-rpc)
(update :categories <-categories-rpc))) (update :categories <-categories-rpc)))
(defn fetch-community-id-input (defn- fetch-community-id-input
[{:keys [db]}] [{:keys [db]}]
(:communities/community-id-input db)) (:communities/community-id-input db))
(defn- handle-my-request
[db {:keys [community-id state] :as request}]
{:db (if (= constants/community-request-to-join-state-pending state)
(assoc-in db [:communities/my-pending-requests-to-join community-id] request)
(update-in db [:communities/my-pending-requests-to-join] dissoc community-id))})
(defn handle-admin-request
[db {:keys [id community-id] :as request}]
{:db (assoc-in db [:communities/requests-to-join community-id id] request)})
(rf/defn handle-request-to-join (rf/defn handle-request-to-join
[{:keys [db]} r] [{:keys [db]} r]
(let [{:keys [id community-id] :as request} (<-request-to-join-community-rpc r)] (let [my-public-key (get-in db [:multiaccount :public-key])
{:db (assoc-in db [:communities/requests-to-join community-id id] request)})) {:keys [id community-id public-key] :as request} (<-request-to-join-community-rpc r)
my-request? (= my-public-key public-key)]
(if my-request?
(handle-my-request db request)
(handle-admin-request db request))))
(rf/defn handle-removed-chats (rf/defn handle-removed-chats
[{:keys [db]} chat-ids] [{:keys [db]} chat-ids]
@@ -109,6 +125,14 @@
db db
communities)}) communities)})
(rf/defn handle-my-pending-requests-to-join
{:events [:communities/fetched-my-communities-requests-to-join]}
[{:keys [db]} my-requests]
{:db (assoc db
:communities/my-pending-requests-to-join
(<-requests-to-join-community-rpc (types/js->clj my-requests)
:communityId))})
(rf/defn handle-response (rf/defn handle-response
[_ response-js] [_ response-js]
{:dispatch [:sanitize-messages-and-process-response response-js]}) {:dispatch [:sanitize-messages-and-process-response response-js]})
@@ -130,6 +154,7 @@
[cofx response-js] [cofx response-js]
(let [[event-name _] (:event cofx) (let [[event-name _] (:event cofx)
community-name (aget response-js "communities" 0 "name")] community-name (aget response-js "communities" 0 "name")]
(js/console.log "event-name")
(rf/merge cofx (rf/merge cofx
(handle-response cofx response-js) (handle-response cofx response-js)
(toasts/upsert {:icon :correct (toasts/upsert {:icon :correct
@@ -139,9 +164,31 @@
:t/requested-to-join-community) :t/requested-to-join-community)
{:community community-name})})))) {:community community-name})}))))
(rf/defn requested-to-join
{:events [:communities/requested-to-join]}
[cofx response-js]
(let [community-name (aget response-js "communities" 0 "name")]
(rf/merge cofx
(handle-response cofx response-js)
(toasts/upsert {:icon :correct
:icon-color (:positive-01 @colors/theme)
:text (i18n/label
:t/requested-to-join-community
{:community community-name})}))))
(rf/defn cancelled-requested-to-join
{:events [:communities/cancelled-request-to-join]}
[cofx response-js]
(rf/merge cofx
(handle-response cofx response-js)
(toasts/upsert {:icon :correct
:icon-color (:positive-01 @colors/theme)
:text (i18n/label
:t/you-canceled-the-request)})))
(rf/defn export (rf/defn export
{:events [::export-pressed]} {:events [::export-pressed]}
[cofx community-id] [_ community-id]
{:json-rpc/call [{:method "wakuext_exportCommunity" {:json-rpc/call [{:method "wakuext_exportCommunity"
:params [community-id] :params [community-id]
:on-success #(re-frame/dispatch [:show-popover :on-success #(re-frame/dispatch [:show-popover
@@ -175,14 +222,35 @@
(rf/defn request-to-join (rf/defn request-to-join
{:events [:communities/request-to-join]} {:events [:communities/request-to-join]}
[cofx community-id] [_ community-id]
{:json-rpc/call [{:method "wakuext_requestToJoinCommunity" {:json-rpc/call [{:method "wakuext_requestToJoinCommunity"
:params [{:communityId community-id}] :params [{:communityId community-id}]
:js-response true :js-response true
:on-success #(re-frame/dispatch [::requested-to-join %]) :on-success #(re-frame/dispatch [:communities/requested-to-join %])
:on-error #(do :on-error (fn []
(log/error "failed to request to join community" community-id %) (log/error "failed to request to join community" community-id)
(re-frame/dispatch [::failed-to-request-to-join %]))}]}) (re-frame/dispatch [::failed-to-request-to-join]))}]})
(rf/defn get-user-requests-to-join
{:events [:communities/get-user-requests-to-join]}
[_]
{:json-rpc/call [{:method "wakuext_myPendingRequestsToJoin"
:params []
:js-response true
:on-success #(re-frame/dispatch
[:communities/fetched-my-communities-requests-to-join %])
:on-error #(log/error "failed to get requests to join community")}]})
(rf/defn cancel-request-to-join
{:events [:communities/cancel-request-to-join]}
[_ request-to-join-id]
{:json-rpc/call [{:method "wakuext_cancelRequestToJoinCommunity"
:params [{:id request-to-join-id}]
:on-success #(re-frame/dispatch [:communities/cancelled-request-to-join %])
:js-response true
:on-error #(log/error "failed to cancel request to join community"
request-to-join-id
%)}]})
(rf/defn leave (rf/defn leave
{:events [:communities/leave]} {:events [:communities/leave]}
@@ -196,11 +264,19 @@
:params [community-id] :params [community-id]
:js-response true :js-response true
:on-success #(re-frame/dispatch [::left %]) :on-success #(re-frame/dispatch [::left %])
:on-error #(do :on-error (fn []
(log/error "failed to leave community" (log/error "failed to leave community"
community-id community-id)
%) (re-frame/dispatch [::failed-to-leave]))}]}))
(re-frame/dispatch [::failed-to-leave %]))}]}))
(rf/defn status-tag-pressed
{:events [:communities/status-tag-pressed]}
[{:keys [db] :as cofx} community-id literal]
(let [{:keys [id]} (some #(when (= (:name %) literal) %)
(vals (get-in db [:communities community-id :chats])))]
(when (and id
(not= (:current-chat-id db) (str community-id id)))
(chat.events/navigate-to-chat cofx (str community-id id) nil))))
(rf/defn fetch (rf/defn fetch
[_] [_]
@@ -545,7 +621,7 @@
[{:keys [db]} community-id requests] [{:keys [db]} community-id requests]
{:db (assoc-in db {:db (assoc-in db
[:communities/requests-to-join community-id] [:communities/requests-to-join community-id]
(<-requests-to-join-community-rpc requests))}) (<-requests-to-join-community-rpc requests :id))})
(rf/defn fetch-requests-to-join (rf/defn fetch-requests-to-join
{:events [::fetch-requests-to-join]} {:events [::fetch-requests-to-join]}
+4
View File
@@ -66,3 +66,7 @@
(update :message #(when % (messages/<-rpc %))) (update :message #(when % (messages/<-rpc %)))
(update :reply-message #(when % (messages/<-rpc %))) (update :reply-message #(when % (messages/<-rpc %)))
(dissoc :chatId))) (dissoc :chatId)))
(defn <-rpc-seen-state
[item]
(:hasSeen item))

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