Compare commits

...
Author SHA1 Message Date
Siddarth Kumar a6b19c021f swap react-native-mail fork with patch (#19688)
## Summary

This commit swaps the fork of react-native-mail with a patch.

needed for : https://github.com/status-im/status-mobile/issues/18138

## Review & Test notes

Verify if mailing logs still works.

## Platforms
- iOS
- Android

status: ready
2024-04-18 10:59:54 +02:00
Sean Hagstrom a258717e83 chore: update status-go version for account-saving fixes (#19612) 2024-04-18 10:37:11 +02:00
Yevheniia Berdnyk a8395e51a4 e2e: GH report: rerun tests link update 2024-04-18 05:41:36 +03:00
codemaster db501b5efd fix: missing author info in reply messages (#19563) 2024-04-17 15:53:23 -07:00
Siddarth Kumar 44732e9736 upgrade camera-roll lib to 7.5.2 (#19664)
This commit swaps the fork of @react-native-camera-roll/camera-roll with a patch and upgrades this library to the latest version.

needed for : https://github.com/status-im/status-mobile/issues/18138

Verify if camera album related features still work.

- iOS

status: ready
2024-04-17 17:14:18 +02:00
Brian Sztamfater d12c05aab6 fix: add external icon to OpenSea button in collectible page (#19647)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-04-17 11:58:27 -03:00
Siddarth Kumar 4134d18cbc nuke unused lib rn-snoopy (#19691)
## Summary

We don't use `rn-snoopy` anymore and this commit nukes it.
2024-04-17 15:27:00 +02:00
Sean Hagstrom 8600c1d3a5 Allow for env variables for configuring delete-message undo time limit for toasts (#19653)
* tweak: allow for env variable to configure undo-time-limit when deleting a message in chat

* tweak: add env variable undo timeout for deleting messages for everyone

* tweak: extend timeout duration for undo toast when deleting a message for a user

* tweak: extend timeout duration for undo toast when deleting a message for everyone
2024-04-17 15:12:27 +02:00
flexsurfer 570c62e30e replace ratom in quo wallet component (#19687) 2024-04-17 14:30:33 +02:00
flexsurfer 549efdb77c Show the users that the keycard feature will be coming soon (#19686) 2024-04-17 13:58:03 +02:00
Parvesh Monu 8807a63989 fix role is not updated correctly when unselecting accounts (#19636) 2024-04-17 16:21:32 +05:30
Alexander c0be0b7670 Update QR code scanner to handle channel and community links (#19507)
* Experiment

* Fix

* Fix

* Fix
2024-04-17 12:27:01 +02:00
John Ngei 6e854428f2 fix: pinned messages empty state drawer margin issues 2024-04-17 10:28:49 +02:00
Icaro Motta c81dd8e3e3 Don't treat dynamic data as a function (#19672)
Sometimes an integration test was throwing "TypeError: Cannot read properties of
null (reading 'call')" because a value was nil in the app-db, but the code was
treating it as if it was always a map and could be called as a function.

The original code wasn't respecting Clojure's nil punning good practices. In
Clojure, it's often recommended to not use dynamic data as functions, because if
they're nil, in CLJS we'll get an exception and as usual in CLJS, the stacktrace
won't be very readable.

We should prefer contains? or get, which will work just fine with nil values,
and only use data as a function if it's static, e.g. a map defined in a def.
2024-04-16 14:48:28 -03:00
Omar Basem 24f4c4a07e Remove watch-only accounts from bridging (#19569)
Remove watch-only accounts from bridging (#19569)
2024-04-16 20:33:03 +04:00
Ajay Sivan 98762c74f1 Community detail token gating component (#19532) 2024-04-16 09:11:18 -07:00
Ibrahem Khalil f3aeddb53a Update doc/troubleshooting.md (#19673) 2024-04-16 17:46:14 +02:00
mmilad75 f5fcc9f45e Wallet: Token Options should be specific to watch only account #18543 (#19583) 2024-04-16 19:02:50 +03:30
Parvesh Monu 9aa9dc1b4c Redirect to community channel when following universal link (#19566) 2024-04-16 20:30:24 +05:30
Sean Hagstrom 61974dae4d Implement share button inside profile settings (#19559)
* tweak: implement the share button inside the profile settings page

* chore: define event and effect handler for opening share sheet

* tidy: refactor share buttons to use open-share event

* tidy: refactor open-share effect to use react-native-share bindings

* tidy: remove unused code for old bindings to react share sheet

* tidy: move effects.share/open definition to navigation effects

* tweak: ensure navigation effects are loaded

* tidy: use open-share dispatch in ui

* tidy: use open-share dispatch instead of calling open function directly

* tidy: refactor react-native.share/open to only receive options and return promise

* tidy: refactor open-share effect to receive map of content and handlers

* tidy: replace strings with keywords

* tweak: always handle failure case when opening share-sheet

* tweak: rename :content to :options

* fix: conditionally call on-error

* tweak: add extra information when logging error from attempting to share-sheet
2024-04-16 13:26:52 +02:00
Parvesh Monu 8b3e639abd fix error on long tap on token-gated channel from the join community screen (#19652) 2024-04-16 16:33:09 +05:30
jakub 7c901fa045 ci: use virtualenv to avoid race conditions
Desktop QA tests also use Pytest packages and their versions are
different, so we can't install them globally, it needs to be done per
build using `WORKSPACE_TMP` as destination.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2024-04-16 13:13:53 +03:00
frank 8a12ad3510 fix_: logout deadlock (#19640) 2024-04-16 17:50:47 +08:00
Brian Sztamfater c8044cd2f2 fix: wallet header spacing to match other tabs (#19632) 2024-04-16 06:31:09 -03:00
Sean Hagstrom 6872d1a857 fix: do not throw exception when passing nil to utils.address/get-abbreviated-profile-url (#19593) 2024-04-16 09:51:35 +02:00
Brian Sztamfater 98d4969ca5 feat: add ability to tap to disable from networks (#19392) 2024-04-15 12:04:44 -03:00
Parvesh Monu 5412092ca1 fix zero addresses are selected by default when joining a community (#19626) 2024-04-15 17:30:31 +05:30
Parvesh Monu a8695116e2 fix error is shown after scan button is tapped on the browser page (#19601) 2024-04-15 15:24:09 +05:30
Yevheniia Berdnyk 76773a4165 e2e: fix for sharing community flow 2024-04-15 12:11:24 +03:00
yqrashawn 32fc7b14e8 fix: add/rename missing sub :current-chat/one-to-one-chat? (#19628)
Signed-off-by: yqrashawn <namy.19@gmail.com>
2024-04-15 17:10:12 +08:00
Sean Hagstrom 82a8f9f44d fix: rewire legacy community share button to send invites (#19622) 2024-04-15 10:52:18 +02:00
John Ngei 8df72d0c29 updated standard auth drawer title size and weight 2024-04-13 23:04:49 +02:00
Mohsen 794cd57961 [#19299] fix: design feedback for settings screen (#19609) 2024-04-12 19:37:07 +03:00
John Ngei 6bc29088ab fix: community spacing issues in token gating community page
* fix: community spacing issues in token gating community page

* fix: spacing issues between token rows
2024-04-12 17:35:03 +02:00
codemaster 658f62e8ce Composer-photo selection drawer issues (#19613) 2024-04-12 07:44:35 -07:00
flexsurfer e6b346ca32 migrate reagent part 7 (#19316) 2024-04-12 15:46:17 +02:00
yqrashawn 00b0755a73 feat: system messge icon based on contact customization color (#19558)
Signed-off-by: yqrashawn <namy.19@gmail.com>
2024-04-12 20:46:09 +08:00
yqrashawn f8cc85f5e2 fix: vertical align texts in multiline input on iOS (#19565)
* fix: vertical align texts in multiline input on iOS

Signed-off-by: yqrashawn <namy.19@gmail.com>

* fix(copy): Chat Key -> Chatkey

---------

Signed-off-by: yqrashawn <namy.19@gmail.com>
2024-04-12 20:25:04 +08:00
Ibrahem Khalil 6ba89d0c6e Community channel avatar is missing on Channel screen (#19459) 2024-04-12 01:18:21 +02:00
Ibrahem Khalil 2614131426 [18979] Implement context action Invite contacts to community (#18989) 2024-04-12 00:12:02 +02:00
flexsurfer cfc43be3cc migrate reagent part6 (#19270) 2024-04-11 16:43:33 +02:00
flexsurfer 3164cc6a6c Chore: setup feature-flag for not-implemented settings (#19591) 2024-04-11 16:43:09 +02:00
Mohsen 5badb5bdb5 [#19178] fix: jump card responsive (#19600) 2024-04-11 17:36:28 +03:00
flexsurfer 292fd78ff8 warning fixes (#19605) 2024-04-11 15:42:29 +02:00
Lungu Cristian c875fb33fa Show token requirements only for token gated channels (#19529)
* fix: use role-permissions? instead of token-permission

token-permissions contains channel-permissions as well, which don't
make the community token-gated

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: hide token requirements from open channels

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fixed tests

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

---------

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>
2024-04-11 16:27:18 +03:00
jakub b5962fd83a nix: fix detecting install type in Docker
Otherwise build fails in a Docker container for Desktop:
```
+ make status-go
Building: status-go
Unknown Nix installtion type!
Unknown Nix installtion type!
make[1]: *** [Makefile:223: statusgo-shared-library] Error 1
make: *** [Makefile:432: vendor/status-go/build/bin/libstatus.so] Error 2
```

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2024-04-11 16:01:34 +03:00
Jamie Caprani 1d1eae13a9 chore(wallet): move add-account funcitonality into its own folder (#19476) 2024-04-11 12:01:35 +02:00
204 changed files with 3369 additions and 2118 deletions
+1
View File
@@ -35,3 +35,4 @@ LOCAL_PAIRING_ENABLED=1
TEST_STATEOFUS=1
FAST_CREATE_COMMUNITY_ENABLED=1
TEST_NETWORKS_ENABLED=1
SHOW_NOT_IMPLEMENTED_FEATURES=1
+3
View File
@@ -34,3 +34,6 @@ STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
FAST_CREATE_COMMUNITY_ENABLED=1
TEST_NETWORKS_ENABLED=1
SHOW_NOT_IMPLEMENTED_FEATURES=1
DELETE_MESSAGE_FOR_ME_UNDO_TIME_LIMIT=10000
DELETE_MESSAGE_UNDO_TIME_LIMIT=10000
+1
View File
@@ -36,3 +36,4 @@ STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
FAST_CREATE_COMMUNITY_ENABLED=1
TEST_NETWORKS_ENABLED=1
SHOW_NOT_IMPLEMENTED_FEATURES=1
+8 -1
View File
@@ -36,6 +36,12 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Fetch') {
steps { script {
@@ -52,7 +58,8 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh 'pip3 install --user -r requirements.txt'
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
}
} }
}
+8 -1
View File
@@ -54,6 +54,12 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Prep') {
steps { script {
@@ -79,7 +85,8 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh 'pip3 install --user -r requirements.txt'
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
}
} }
}
+7 -1
View File
@@ -35,6 +35,11 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Prep') {
@@ -51,7 +56,8 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh 'pip3 install --user -r requirements.txt'
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
}
} }
}
+16
View File
@@ -128,3 +128,19 @@ Status-mobile uses `shadow-cljs` for hot reloading changes and uses its own [rel
### Solution
Open react native's [In-App Developer Menu](https://reactnative.dev/docs/debugging#accessing-the-in-app-developer-menu) and press "Disable Fast Refresh" or "Disable Hot Reloading"
# App crashing after running `make run-ios`
### Cause
It's possible that installing XCode from a `.xip` file might cause XCode to act funny
Since it's not installed from the App Store, Or you might be missing a Rosetta installation.
[Reference/Similar issue](https://github.com/expo/expo-cli/issues/3197)
You might see something like this after running `make run-ios`:
```
Underlying error (domain=FBSOpenApplicationServiceErrorDomain, code=1):
```
### Solution
Run `softwareupdate --install-rosetta --agree-to-license`
to accept XCode's license and install Rosetta.
+4 -2
View File
@@ -881,7 +881,9 @@ PODS:
- React-Core
- react-native-blur (4.3.3):
- React-Core
- react-native-cameraroll (5.10.0):
- react-native-cameraroll (7.5.2):
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
- react-native-config (1.5.0):
- react-native-config/App (= 1.5.0)
@@ -1475,7 +1477,7 @@ SPEC CHECKSUMS:
react-native-biometrics: 352e5a794bfffc46a0c86725ea7dc62deb085bdc
react-native-blob-util: 600972b1782380a5a7d5db61a3817ea32349dae9
react-native-blur: c6d0a1dc2b4b519f7afe3b14d8151998632b6d18
react-native-cameraroll: 4701ae7c3dbcd3f5e9e150ca17f250a276154b35
react-native-cameraroll: af8eec1e585d053ff485d98ec837f9a8a11b5745
react-native-config: 5330c8258265c1e5fdb8c009d2cabd6badd96727
react-native-hole-view: 6935448993bac79f2b5a4ad7e9741094cf810679
react-native-image-resizer: 2f1577efa3bc762597681f530c8e8d05ce0ceeb3
+1
View File
@@ -27,6 +27,7 @@ nix_install_type() {
if [[ "$(os_name)" =~ NixOS ]]; then
echo "nixos"
else
USER=$(id -un) # Missing in Docker.
case "${NIX_STORE_DIR_GROUP}" in
"nixbld") echo "multi";;
"30000") echo "multi";;
+2 -3
View File
@@ -9,7 +9,7 @@
},
"dependencies": {
"@react-native-async-storage/async-storage": "1.19.3",
"@react-native-camera-roll/camera-roll": "git+https://github.com/status-im/react-native-camera-roll.git#refs/tags/v5.1.1.1",
"@react-native-camera-roll/camera-roll": "7.5.2",
"@react-native-clipboard/clipboard": "1.13.2",
"@react-native-community/audio-toolkit": "git+https://github.com/tbenr/react-native-audio-toolkit.git#refs/tags/v2.0.3-status-v6",
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur.git#refs/tags/v4.3.3-status",
@@ -48,7 +48,7 @@
"react-native-keychain": "8.1.2",
"react-native-linear-gradient": "^2.8.0",
"react-native-lottie-splash-screen": "^1.0.1",
"react-native-mail": "git+https://github.com/status-im/react-native-mail.git#refs/tags/v6.1.2-status",
"react-native-mail": "6.1.1",
"react-native-navigation": "7.38.3",
"react-native-orientation-locker": "^1.5.0",
"react-native-permissions": "3.8.0",
@@ -94,7 +94,6 @@
"prettier": "^2.8.8",
"process": "0.11.10",
"react-test-renderer": "18.1.0",
"rn-snoopy": "git+https://github.com/status-im/rn-snoopy.git#refs/tags/v2.0.2-status",
"shadow-cljs": "2.26.2"
},
"binary": {
+22
View File
@@ -0,0 +1,22 @@
--- /tmp/tmp-status-mobile-3907e6b2e/tmp.re8kHerusA/CameraRoll.ts 2024-04-16 15:17:12.942432000 +0200
+++ ./node_modules/@react-native-camera-roll/camera-roll/src/CameraRoll.ts 2024-04-16 15:17:42.455250986 +0200
@@ -239,6 +239,19 @@
}
/**
+ * Returns total iOS image count
+ */
+ static getPhotosCountiOS(): Promise<number> {
+ return RNCCameraRoll.getPhotosCountiOS('');
+ }
+ /**
+ * Returns favorites and their count iOS
+ */
+ static getFavoritesiOS(): Promise<Album> {
+ return RNCCameraRoll.getFavoritesiOS('');
+ }
+
+ /**
* Saves the photo or video to the camera roll or photo library, and returns the URI of the newly created asset.
*
* @deprecated `save(...)` is deprecated - use `saveAsset(...)` instead.
+11
View File
@@ -0,0 +1,11 @@
--- /tmp/tmp-status-mobile-3907e6b2e/tmp.juxTO1BeCM/NativeCameraRollModule.ts 2024-04-16 15:21:28.379979000 +0200
+++ ./node_modules/@react-native-camera-roll/camera-roll/src/NativeCameraRollModule.ts 2024-04-16 15:21:40.490391291 +0200
@@ -81,6 +81,8 @@
getPhotos(params: Object): Promise<PhotoIdentifiersPage>;
getAlbums(params: Object): Promise<Album[]>;
deletePhotos(photoUris: Array<string>): Promise<void>;
+ getPhotosCountiOS(arg: string): Promise<number>;
+ getFavoritesiOS(arg: string): Promise<Album>;
getPhotoByInternalID(
internalID: string,
options: Object,
+56
View File
@@ -0,0 +1,56 @@
--- /tmp/tmp-status-mobile-3907e6b2e/tmp.O0mkyjqnsy/RNCCameraRoll.mm 2024-04-16 15:26:23.070258000 +0200
+++ ./node_modules/@react-native-camera-roll/camera-roll/ios/RNCCameraRoll.mm 2024-04-16 15:26:32.664996066 +0200
@@ -955,6 +955,53 @@
return [albumTitles copy];
}
+RCT_EXPORT_METHOD(getPhotosCountiOS:(NSString *)blank
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject)
+{
+ __block NSInteger intTotalCount=0;
+ PHFetchOptions *allPhotosOptions = [PHFetchOptions new];
+ allPhotosOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType == %d ",PHAssetMediaTypeImage];
+ PHFetchResult *allPhotosResult = [PHAsset fetchAssetsWithOptions:allPhotosOptions];
+ intTotalCount+=allPhotosResult.count;
+
+ resolve(@(intTotalCount));
+}
+
+RCT_EXPORT_METHOD(getFavoritesiOS:(NSString *)blank
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject)
+{
+ __block NSInteger intTotalCount=0;
+ PHFetchOptions *fetchOptions = [PHFetchOptions new];
+ NSString *format = @"(favorite == true)";
+ fetchOptions.predicate = [NSPredicate predicateWithFormat:format];
+ PHFetchResult<PHAsset *> *const assetsFetchResult = [PHAsset fetchAssetsWithOptions:fetchOptions];
+ PHAsset *imageAsset = [assetsFetchResult firstObject];
+ NSMutableArray * result = [NSMutableArray new];
+
+ for (PHAsset* asset in assetsFetchResult) {
+ NSArray *resources = [PHAssetResource assetResourcesForAsset:asset ];
+ if ([resources count] < 1) continue;
+ NSString *orgFilename = ((PHAssetResource*)resources[0]).originalFilename;
+ NSString *uit = ((PHAssetResource*)resources[0]).uniformTypeIdentifier;
+ NSString *mimeType = (NSString *)CFBridgingRelease(UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(uit), kUTTagClassMIMEType));
+ CFStringRef extension = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(uit), kUTTagClassFilenameExtension);
+ [result addObject:@{
+ @"width": @([asset pixelWidth]),
+ @"height": @([asset pixelHeight]),
+ @"filename": orgFilename ?: @"",
+ @"mimeType": mimeType ?: @"",
+ @"id": [asset localIdentifier],
+ @"creationDate": [asset creationDate],
+ @"uri": [NSString stringWithFormat:@"ph://%@", [asset localIdentifier]],
+ @"duration": @([asset duration])
+ }];
+ }
+ [result addObject:@{@"count": @(assetsFetchResult.count)}];
+ resolve(result);
+}
+
static void checkPhotoLibraryConfig()
{
#if RCT_DEV
@@ -0,0 +1,15 @@
--- /tmp/tmp-status-mobile-6e854428f/tmp.PSruc8WtHx/build.gradle 2024-04-17 11:08:46.857319000 +0200
+++ ./node_modules/react-native-mail/android/build.gradle 2024-04-17 11:09:22.935436792 +0200
@@ -1,7 +1,11 @@
apply plugin: 'com.android.library'
+def safeExtGet(prop, fallback) {
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
+}
+
android {
- compileSdkVersion project.hasProperty('compileSdkVersion') ? project.compileSdkVersion : 23
+ compileSdkVersion safeExtGet('compileSdkVersion', 30)
buildToolsVersion project.hasProperty('buildToolsVersion') ? project.buildToolsVersion : "23.0.1"
defaultConfig {
Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+9 -50
View File
@@ -4,6 +4,7 @@
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
legacy.status-im.communities.e2e
[re-frame.core :as re-frame]
[status-im.contexts.shell.activity-center.events :as activity-center]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
@@ -22,60 +23,10 @@
{}
requests))
(defn- fetch-community-id-input
[{:keys [db]}]
(:communities/community-id-input db))
(rf/defn handle-response
[_ response-js]
{:dispatch [:sanitize-messages-and-process-response response-js]})
(rf/defn invite-users
{:events [::invite-people-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_inviteUsersToCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-invite-people %]))}]})))
(rf/defn share-community
{:events [::share-community-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-share-community %]))}]})))
(re-frame/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {:invite? true}]]]}))
(re-frame/reg-event-fx :communities/share-community-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {}]]]}))
(rf/defn people-invited
{:events [::people-invited]}
[cofx response-js]
@@ -90,6 +41,14 @@
[:sanitize-messages-and-process-response response-js]
[:activity-center.notifications/fetch-unread-count]]}))
(rf/defn member-banned
{:events [::member-banned]}
[cofx response-js]
(rf/merge cofx
(bottom-sheet/hide-bottom-sheet-old)
(handle-response response-js)
(activity-center/notifications-fetch-unread-count)))
(rf/defn member-ban
{:events [::member-ban]}
[cofx community-id public-key]
-16
View File
@@ -38,22 +38,6 @@
:params []
:on-success [:stickers/stickers-recent-success]})))
(rf/defn load-packs
{:events [:stickers/load-packs]}
[{:keys [db]}]
{:json-rpc/call [{:method "stickers_market"
:params [(chain/chain-id db)]
:on-success #(re-frame/dispatch [:stickers/stickers-market-success %])}
{:method "stickers_installed"
:params []
:on-success #(re-frame/dispatch [:stickers/stickers-installed-success %])}
{:method "stickers_pending"
:params []
:on-success #(re-frame/dispatch [:stickers/stickers-pending-success %])}
{:method "stickers_recent"
:params []
:on-success #(re-frame/dispatch [:stickers/stickers-recent-success %])}]})
(rf/defn pending-pack
{:events [:stickers/pending-pack]}
[{db :db} id]
@@ -2,7 +2,6 @@
(:require
[legacy.status-im.browser.core :as browser]
[legacy.status-im.browser.webview-ref :as webview-ref]
[legacy.status-im.qr-scanner.core :as qr-scanner]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.connectivity.view :as connectivity]
@@ -105,9 +104,7 @@
(if empty-tab
[react/touchable-highlight
{:accessibility-label :universal-qr-scanner
:on-press #(re-frame/dispatch
[::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}])}
:on-press #(re-frame/dispatch [:open-modal :shell-qr-reader])}
[icons/icon :main-icons/qr {:color colors/black}]]
[react/touchable-highlight
{:on-press #(re-frame/dispatch
@@ -1,16 +1,15 @@
(ns legacy.status-im.ui.screens.communities.invite
(:require
[clojure.string :as string]
[legacy.status-im.communities.core :as communities]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.toolbar :as toolbar]
[legacy.status-im.ui.components.topbar :as topbar]
[quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -49,18 +48,35 @@
contacts-selected (reagent/atom #{})
{:keys [invite?]} (rf/sub [:get-screen-params])]
(fn []
(let [contacts-data (rf/sub [:contacts/active])
{:keys [permissions
can-manage-users?]}
(let [theme (quo.theme/use-theme)
contacts-data (rf/sub [:contacts/active])
{community-id :id
:keys [permissions
can-manage-users?]}
(rf/sub [:communities/edited-community])
selected @contacts-selected
selected-contacts-count (count selected)
contacts (map (fn [{:keys [public-key] :as contact}]
(assoc contact :active (contains? selected public-key)))
contacts-data)
;; no-membership communities can only be shared
can-invite? (and can-manage-users?
invite?
(not= (:access permissions) constants/community-no-membership-access))]
(not= (:access permissions) constants/community-no-membership-access))
on-press-share-community (rn/use-callback
(fn []
(rf/dispatch [:communities/share-community-confirmation-pressed
selected community-id])
(rf/dispatch [:navigate-back])
(rf/dispatch [:toasts/upsert
{:type :positive
:theme theme
:text (if (= 1 selected-contacts-count)
(i18n/label :t/one-user-was-invited)
(i18n/label
:t/n-users-were-invited
{:count selected-contacts-count}))}]))
[community-id selected selected-contacts-count theme])]
[:<>
[topbar/topbar
{:title (i18n/label (if can-invite?
@@ -81,11 +97,8 @@
:center
[quo/button
{:disabled (and (string/blank? @user-pk)
(zero? (count selected)))
(zero? selected-contacts-count))
:accessibility-label :share-community-link
:type :secondary
:on-press #(debounce/throttle-and-dispatch
[::communities/share-community-confirmation-pressed @user-pk
selected]
3000)}
:on-press on-press-share-community}
(i18n/label (if can-invite? :t/invite :t/share))]}]]))))
@@ -13,3 +13,11 @@
:background-color (colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
theme)})
(defn avatar-identifier
[theme]
{:text-align :center
:font-size 36
:color (colors/theme-colors colors/black
colors/white
theme)})
@@ -1,7 +1,9 @@
(ns quo.components.avatars.group-avatar.view
(:require
[clojure.string :as string]
[quo.components.avatars.group-avatar.style :as style]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
@@ -21,7 +23,7 @@
(defn- view-internal
[_]
(fn [{:keys [size theme customization-color picture icon-name]
(fn [{:keys [size theme customization-color picture icon-name emoji chat-name]
:or {size :size-20
customization-color :blue
picture nil
@@ -38,8 +40,27 @@
{:source picture
:style {:width container-size
:height container-size}}]
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}])])))
(cond
emoji
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
emoji]
[text/text
{:size :paragraph-1
:style (dissoc (style/avatar-identifier theme) :font-size)}
emoji])
chat-name
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
((comp first string/upper-case) chat-name)]
[text/text
{:size :paragraph-1}
((comp first string/upper-case) chat-name)])
:else
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}]))])))
(def view (quo.theme/with-theme view-internal))
+6 -6
View File
@@ -28,9 +28,9 @@
:theme :light/:dark
only icon
[button {:icon-only? true} :i/close-circle]"
[{:keys [on-press on-long-press disabled? type background size icon-left icon-right icon-top
customization-color accessibility-label icon-only? container-style inner-style
pressed? on-press-in on-press-out allow-multiple-presses?]
[{:keys [on-press on-long-press disabled? type background size icon-left icon-left-color icon-right
icon-right-color icon-top icon-top-color customization-color accessibility-label icon-only?
container-style inner-style pressed? on-press-in on-press-out allow-multiple-presses?]
:or {type :primary
size 40
customization-color (if (= type :primary) :blue nil)}}
@@ -94,7 +94,7 @@
[quo.icons/icon icon-top
{:container-style {:margin-bottom 2
:opacity (when disabled? 0.3)}
:color icon-color
:color (or icon-top-color icon-color)
:size icon-size}]])
(when icon-left
[rn/view
@@ -103,7 +103,7 @@
:icon-size icon-size
:disabled? disabled?})}
[quo.icons/icon icon-left
{:color icon-color
{:color (or icon-left-color icon-color)
:size icon-size}]])
[rn/view
(cond
@@ -130,5 +130,5 @@
:icon-size icon-size
:disabled? disabled?})}
[quo.icons/icon icon-right
{:color icon-color
{:color (or icon-right-color icon-color)
:size icon-size}]])]]]))
@@ -0,0 +1,71 @@
(ns quo.components.community.community-detail-token-gating.component-spec
(:require
[quo.core :as quo]
[test-helpers.component :as h]))
(h/describe "Community Detail Token Gating Component"
(h/test "render with member permissions"
(h/render
[quo/community-detail-token-gating
{:permissions [{:role 2
:role-text "Member"
:satisfied? true
:tokens [[{:symbol "ETH"
:sufficient? true
:collectible? false
:amount 0.8
:img-src nil}]
[{:symbol "ETH"
:sufficient? false
:collectible? false
:amount 1
:img-src nil}
{:symbol "STT"
:sufficient? false
:collectible? false
:amount 10
:img-src nil}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Member"}))
(h/is-truthy (h/get-by-text "0.8 ETH"))
(h/is-truthy (h/get-by-text "1 ETH"))
(h/is-truthy (h/get-by-text "10 STT")))
(h/test "render with admin permissions"
(h/render
[quo/community-detail-token-gating
{:permissions [{:role 1
:role-text "Admin"
:satisfied? true
:tokens [[{:symbol "ETH"
:sufficient? true
:collectible? false
:amount 2
:img-src nil}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Admin"}))
(h/is-truthy (h/get-by-text "2 ETH")))
(h/test "render with token master permissions"
(h/render-with-theme-provider
[quo/community-detail-token-gating
{:permissions [{:role 5
:role-text "Token Master"
:satisfied? true
:tokens [[{:symbol "TMANI "
:sufficient? true
:collectible? true
:img-src {:uri "mock-image"}}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Token Master"}))
(h/is-truthy (h/get-by-text "TMANI")))
(h/test "render with token owner permissions"
(h/render-with-theme-provider
[quo/community-detail-token-gating
{:permissions [{:role 6
:role-text "Token Owner"
:satisfied? true
:tokens [[{:symbol "TOANI"
:sufficient? true
:collectible? true
:img-src {:uri "mock-image"}}]]}]}])
(h/is-truthy (h/get-by-translation-text :t/you-eligible-to-join-as {:role "Token Owner"}))
(h/is-truthy (h/get-by-text "TOANI"))))
@@ -0,0 +1,20 @@
(ns quo.components.community.community-detail-token-gating.style)
(def container
{:padding-horizontal 20
:margin-vertical -4})
(defn token-row
[first?]
{:flex-direction :row
:margin-top (if first? 8 4)
:row-gap 10
:column-gap 8
:flex-wrap :wrap
:margin-bottom 16})
(def divider
{:padding-left 0
:height 28
:padding-top 0
:align-items :flex-start})
@@ -0,0 +1,74 @@
(ns quo.components.community.community-detail-token-gating.view
(:require [clojure.string :as string]
[quo.components.community.community-detail-token-gating.style :as style]
[quo.components.dividers.divider-label.view :as divider-label]
[quo.components.markdown.text :as text]
[quo.components.tags.collectible-tag.view :as collectible-tag]
[quo.components.tags.token-tag.view :as token-tag]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[utils.i18n :as i18n]))
(defn- token-view
[{:keys [collectible? img-src amount sufficient?] :as token}]
(let [token-symbol (:symbol token)]
(if collectible?
[collectible-tag/view
{:collectible-name token-symbol
:size :size-24
:collectible-img-src img-src
:options (when sufficient?
:hold)}]
[token-tag/view
{:token-symbol token-symbol
:size :size-24
:token-value amount
:token-img-src img-src
:options (when sufficient? :hold)}])))
(defn- tokens-row
[{:keys [tokens divider? first?]}]
[:<>
[rn/view
{:style (style/token-row first?)}
(map-indexed (fn [token-index token]
^{:key (str "token-" token-index)}
[token-view token])
tokens)]
(when-not divider?
[divider-label/view
{:container-style style/divider}
[text/text
{:size :label
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(string/lower-case (i18n/label :t/or))]])])
(defn- role-view
[{:keys [role tokens satisfied? role-text]}]
(when (seq tokens)
^{:key (str "role-" role)}
[:<>
[text/text
{:size :paragraph-1
:weight :medium
:style {:margin-top 4}}
(if satisfied?
(i18n/label :t/you-eligible-to-join-as {:role role-text})
(i18n/label :t/you-not-eligible-to-join-as {:role role-text}))]
[text/text
{:size :paragraph-2
:style {:margin-bottom 8}}
(i18n/label (if satisfied? :t/you-hodl :t/you-must-hold))]
(map-indexed (fn [index tokens-item]
^{:key (str role "-tokens-" index)}
[tokens-row
{:tokens tokens-item
:first? (zero? index)
:divider? (= index (dec (count tokens)))}])
tokens)]))
(defn view
[{:keys [permissions]}]
[rn/view {:style style/container}
(map role-view permissions)])
+7 -6
View File
@@ -102,7 +102,7 @@
:right 8})
(def token-tag-spacing
{:margin-top 10
{:padding-top 10
:margin-right 8})
(defn token-row
@@ -117,11 +117,12 @@
(defn token-row-or-text
[padding? theme]
(merge
{:margin-top 4
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)}
{:padding-top 4
:margin-bottom -2
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)}
(when padding?
{:padding-left 12})))
@@ -7,8 +7,14 @@
:align-items :center})
(def image
{:width 80
:height 80})
{:width 72
:height 72})
(def image-container
{:width 80
:height 80
:align-items :center
:justify-content :center})
(def text-container
{:margin-top 12
@@ -15,9 +15,10 @@
[rn/view {:style (merge styles/container container-style)}
(if placeholder?
[rn/view {:style styles/image-placeholder}]
[fast-image/fast-image
{:style styles/image
:source image}])
[rn/view {:style styles/image-container}
[fast-image/fast-image
{:style styles/image
:source image}]])
[rn/view {:style styles/text-container}
[text/text
{:style (styles/title blur?)
+8 -4
View File
@@ -1,7 +1,8 @@
(ns quo.components.inputs.input.style
(:require
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]))
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
(defn variants-colors
[blur? theme]
@@ -97,9 +98,12 @@
(assoc base-props
:text-align-vertical :top
:line-height 22)
(assoc base-props
:height (if small? 30 38)
:line-height nil))))
(cond-> base-props
:always
(assoc :height (if small? 30 38)
:line-height nil)
platform/ios?
(assoc :padding-top (+ padding 2))))))
(defn right-icon-touchable-area
[small?]
+7 -4
View File
@@ -41,21 +41,24 @@
(defn user
[{:keys [short-chat-key primary-name secondary-name photo-path online? contact? verified?
untrustworthy? on-press on-long-press accessory customization-color theme
allow-multiple-presses?]}]
allow-multiple-presses? disabled?]}]
[rn/touchable-highlight
{:style container-style
:underlay-color (colors/resolve-color customization-color theme 5)
:allow-multiple-presses? allow-multiple-presses?
:accessibility-label :user-list
:on-press (when on-press on-press)
:on-long-press (when on-long-press on-long-press)}
:on-long-press (when on-long-press on-long-press)
:disabled disabled?}
[:<>
[user-avatar/user-avatar
{:full-name primary-name
:profile-picture photo-path
:online? online?
:size :small}]
[rn/view {:style {:margin-horizontal 8 :flex 1}}
[rn/view
{:style {:margin-horizontal 8
:flex 1}}
[author/view
{:primary-name primary-name
:secondary-name secondary-name
@@ -69,4 +72,4 @@
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
short-chat-key])]
(when accessory
[action-icon accessory customization-color theme])]])
[action-icon accessory customization-color disabled? theme])]])
@@ -9,11 +9,10 @@
[quo.components.tags.status-tags :as status-tags]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(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 set-reply-input]
[rn/view
[rn/view
{:style {:margin-top 16
@@ -26,12 +25,12 @@
(i18n/label :t/your-answer)]
[text/text
{:style {:flex-shrink 1
:color (if (valid-reply? @reply-input)
:color (if (valid-reply? reply-input)
colors/neutral-40
colors/danger-60)}}
(str (count @reply-input) "/" max-reply-length)]]
(str (count reply-input) "/" max-reply-length)]]
[input/input
{:on-change-text #(do (reset! reply-input %)
{:on-change-text #(do (set-reply-input %)
(when on-update-reply
(on-update-reply %)))
:auto-capitalize :none
@@ -136,7 +135,7 @@
(-> button
(assoc :size size)
(assoc :type subtype)
(assoc :disabled? (and replying? disable-when (disable-when @reply-input)))
(assoc :disabled? (and replying? disable-when (disable-when reply-input)))
(assoc :inner-style
{:justify-content :center
:padding-bottom 0
@@ -153,17 +152,16 @@
:blur? blur?}])
(defn- footer
[_]
(let [reply-input (reagent/atom "")]
(fn [{:keys [replying? items] :as props}]
[:<>
(when replying?
[activity-reply-text-input props reply-input])
(when items
[rn/view style/footer-container
(for [item items]
^{:key (:key item)}
[footer-item-view item replying? reply-input])])])))
[{:keys [replying? items] :as props}]
(let [[reply-input set-reply-input] (rn/use-state "")]
[:<>
(when replying?
[activity-reply-text-input props reply-input set-reply-input])
(when items
[rn/view style/footer-container
(for [item items]
^{:key (:key item)}
[footer-item-view item replying? reply-input])])]))
(defn view
[{:keys [icon
@@ -4,8 +4,7 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]
[reagent.core :as reagent]))
[react-native.svg :as svg]))
(defn- get-path-props
[size stroke-width rotation]
@@ -49,60 +48,53 @@
{:color {:dark colors/neutral-80-opa-40
:light colors/white-opa-40}})
(defn- circle-timer-internal
[{:keys [color duration size stroke-width trail-color rotation initial-remaining-time theme]}]
(let [rotation (or rotation :clockwise)
duration (or duration 4)
stroke-width (or stroke-width 1)
size (or size 9)
max-stroke-width (max stroke-width 0)
{:keys [path path-length]} (get-path-props size max-stroke-width rotation)
start-at (get-start-at duration initial-remaining-time)
elapsed-time (reagent/atom 0)
prev-frame-time (reagent/atom nil)
frame-request (reagent/atom nil)
display-time (reagent/atom start-at)
;; get elapsed frame time
swap-elapsed-time-each-frame (fn swap-elapsed-time-each-frame [frame-time]
(if (nil? @prev-frame-time)
(do (reset! prev-frame-time frame-time)
(reset! frame-request (js/requestAnimationFrame
swap-elapsed-time-each-frame)))
(let [delta (- (/ frame-time 1000)
(/ @prev-frame-time 1000))
current-elapsed (swap! elapsed-time + delta)
current-display-time (+ start-at current-elapsed)
completed? (>= current-display-time duration)]
(reset! display-time (if completed?
duration
current-display-time))
(when-not completed?
(reset! prev-frame-time frame-time)
(reset! frame-request (js/requestAnimationFrame
swap-elapsed-time-each-frame))))))]
(reagent/create-class
{:component-will-unmount #(js/cancelAnimationFrame @frame-request)
:reagent-render
(fn []
(reset! frame-request (js/requestAnimationFrame swap-elapsed-time-each-frame))
[rn/view
{:style {:position :relative
:width size
:height size}}
[svg/svg
{:view-box (str "0 0 " size " " size)
:width size
:height size}
[svg/path
{:d path :fill :none :stroke (or trail-color :transparent) :stroke-width stroke-width}]
(when-not (= @display-time duration)
[svg/path
{:d path
:fill :none
:stroke (or color (get-in themes [:color theme]))
:stroke-linecap :square
:stroke-width stroke-width
:stroke-dasharray path-length
:stroke-dashoffset (linear-ease @display-time 0 path-length duration)}])]])})))
(def circle-timer (quo.theme/with-theme circle-timer-internal))
(defn circle-timer
[{:keys [color duration size stroke-width trail-color rotation initial-remaining-time]}]
(let [theme (quo.theme/use-theme-value)
rotation (or rotation :clockwise)
duration (or duration 4)
stroke-width (or stroke-width 1)
size (or size 9)
max-stroke-width (max stroke-width 0)
{:keys [path path-length]} (get-path-props size max-stroke-width rotation)
start-at (get-start-at duration initial-remaining-time)
elapsed-time (rn/use-ref-atom 0)
prev-frame-time (rn/use-ref-atom nil)
frame-request (rn/use-ref-atom nil)
[display-time set-display-time] (rn/use-state start-at)
swap-elapsed-time-each-frame (fn swap-elapsed-time-each-frame [frame-time]
(if (nil? @prev-frame-time)
(do (reset! prev-frame-time frame-time)
(reset! frame-request (js/requestAnimationFrame
swap-elapsed-time-each-frame)))
(let [delta (- (/ frame-time 1000)
(/ @prev-frame-time 1000))
current-elapsed (swap! elapsed-time + delta)
current-display-time (+ start-at current-elapsed)
completed? (>= current-display-time
duration)]
(set-display-time
(if completed? duration current-display-time))
(when-not completed?
(reset! prev-frame-time frame-time)))))]
(rn/use-effect #(reset! frame-request (js/requestAnimationFrame swap-elapsed-time-each-frame)))
(rn/use-unmount #(js/cancelAnimationFrame @frame-request))
[rn/view
{:style {:position :relative
:width size
:height size}}
[svg/svg
{:view-box (str "0 0 " size " " size)
:width size
:height size}
[svg/path
{:d path :fill :none :stroke (or trail-color :transparent) :stroke-width stroke-width}]
(when-not (= display-time duration)
[svg/path
{:d path
:fill :none
:stroke (or color (get-in themes [:color theme]))
:stroke-linecap :square
:stroke-width stroke-width
:stroke-dasharray path-length
:stroke-dashoffset (linear-ease display-time 0 path-length duration)}])]]))
@@ -15,10 +15,10 @@
[rn/touchable-highlight
{:on-press on-press
:underlay-color :transparent}
[into
(into
[rn/view
{:style (merge (style/action-container theme) style)}]
children]])
children)])
(defn toast-undo-action-internal
[{:keys [undo-duration undo-on-press theme]}]
+16 -19
View File
@@ -3,23 +3,20 @@
[quo.components.icon :as icon]
[quo.components.selectors.filter.style :as style]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn view-internal
[initial-props]
(let [pressed? (reagent/atom (:pressed? initial-props))]
(fn [{:keys [blur? customization-color theme on-press-out]}]
[rn/touchable-without-feedback
{:accessibility-label :selector-filter
:on-press-out (fn []
(swap! pressed? not)
(when on-press-out
(on-press-out @pressed?)))}
[rn/view {:style (style/container-outer customization-color @pressed? theme)}
[rn/view {:style (style/container-inner @pressed? blur? theme)}
[icon/icon :i/unread
{:color (style/icon-color @pressed? theme)
:size 20}]]]])))
(def view (quo.theme/with-theme view-internal))
(defn view
[{:keys [blur? customization-color on-press-out pressed?]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state pressed?)
on-press-out (fn []
(set-pressed (not pressed?))
(when on-press-out (on-press-out pressed?)))]
[rn/touchable-without-feedback
{:accessibility-label :selector-filter
:on-press-out on-press-out}
[rn/view {:style (style/container-outer customization-color pressed? theme)}
[rn/view {:style (style/container-inner pressed? blur? theme)}
[icon/icon :i/unread
{:color (style/icon-color pressed? theme)
:size 20}]]]]))
@@ -2,23 +2,20 @@
(:require
[quo.components.selectors.reaction-resource :as reactions.resource]
[quo.components.selectors.reactions-selector.style :as style]
[react-native.core :as rn]
[reagent.core :as reagent]))
[react-native.core :as rn]))
(defn view
[{:keys [start-pressed?]}]
(let [pressed? (reagent/atom start-pressed?)]
(fn [{:keys [emoji container-style on-press
accessibility-label]
:or {accessibility-label :reaction}}]
[rn/pressable
{:accessibility-label accessibility-label
:allow-multiple-presses? true
:style (merge (style/container @pressed?)
container-style)
:on-press (fn [e]
(swap! pressed? not)
(when on-press
(on-press e)))}
[rn/text
(reactions.resource/system-emojis emoji)]])))
[{:keys [emoji container-style on-press
accessibility-label start-pressed?]
:or {accessibility-label :reaction}}]
(let [[pressed? set-pressed] (rn/use-state start-pressed?)
on-press (fn [e]
(set-pressed (not pressed?))
(when on-press (on-press e)))]
[rn/pressable
{:accessibility-label accessibility-label
:allow-multiple-presses? true
:style (merge (style/container pressed?)
container-style)
:on-press on-press}
[rn/text (reactions.resource/system-emojis emoji)]]))
@@ -3,47 +3,47 @@
[quo.components.icon :as icons]
[quo.components.selectors.selectors.style :as style]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn- handle-press
[on-change checked-atom checked?]
(when checked-atom (swap! checked-atom not))
(when on-change (on-change (not checked?))))
[react-native.core :as rn]))
(defn- base-selector
[{:keys [default-checked? checked?]}]
(let [controlled-component? (some? checked?)
internal-checked? (when-not controlled-component?
(reagent/atom (or default-checked? false)))]
(fn [{:keys [checked? disabled? blur? customization-color on-change container-style
label-prefix outer-style-fn inner-style-fn icon-style-fn theme]
:or {customization-color :blue}}]
(let [actual-checked? (if controlled-component? checked? @internal-checked?)
accessibility-label (str label-prefix "-" (if actual-checked? "on" "off"))
test-id (str label-prefix "-component")
outer-styles (outer-style-fn {:checked? actual-checked?
[{:keys [default-checked? checked? disabled? blur? customization-color on-change container-style
label-prefix outer-style-fn inner-style-fn icon-style-fn theme]
:or {customization-color :blue}}]
(let [controlled-component? (some? checked?)
[internal-checked?
set-internal-checked?] (rn/use-state (when-not controlled-component?
(or default-checked? false)))
actual-checked? (if controlled-component? checked? internal-checked?)
accessibility-label (str label-prefix "-" (if actual-checked? "on" "off"))
test-id (str label-prefix "-component")
outer-styles (outer-style-fn {:checked? actual-checked?
:disabled? disabled?
:blur? blur?
:container-style container-style
:customization-color customization-color
:theme theme})]
[rn/pressable
(when-not disabled?
{:on-press #(handle-press on-change internal-checked? actual-checked?)
:allow-multiple-presses? true})
[rn/view
{:style outer-styles
:needs-offscreen-alpha-compositing true
:accessibility-label accessibility-label
:testID test-id}
[rn/view
{:style (inner-style-fn {:theme theme
:checked? actual-checked?
:blur? blur?
:customization-color customization-color})}
(when (and icon-style-fn actual-checked?)
[icons/icon :i/check-small (icon-style-fn actual-checked? blur? theme)])]]]))))
:theme theme})
on-press (rn/use-callback
(fn []
(when-not (nil? internal-checked?)
(set-internal-checked? (not internal-checked?)))
(when on-change (on-change (not actual-checked?))))
[internal-checked? actual-checked? on-change])]
[rn/pressable
(when-not disabled?
{:on-press on-press
:allow-multiple-presses? true})
[rn/view
{:style outer-styles
:needs-offscreen-alpha-compositing true
:accessibility-label accessibility-label
:testID test-id}
[rn/view
{:style (inner-style-fn {:theme theme
:checked? actual-checked?
:blur? blur?
:customization-color customization-color})}
(when (and icon-style-fn actual-checked?)
[icons/icon :i/check-small (icon-style-fn actual-checked? blur? theme)])]]]))
(defn- toggle
[props]
@@ -8,8 +8,7 @@
[:options {:optional true} [:maybe [:enum :add :hold]]]
[:size {:optional true} [:maybe [:enum :size-24 :size-32]]]
[:blur? {:optional true} [:maybe :boolean]]
[:theme :schema.common/theme]
[:collectible-img-src :schema.common/image-source]
[:collectible-name :string]
[:collectible-id :string]]]]
[:collectible-id {:optional true} [:maybe :string]]]]]
:any])
@@ -8,52 +8,50 @@
[quo.theme]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- view-internal
[]
(let [container-width (reagent/atom 0)
on-layout #(->> (oops/oget % :nativeEvent :layout :width)
(reset! container-width))]
(fn [{:keys [options blur? theme collectible-img-src collectible-name collectible-id] :as props}]
(let [size (or (:size props) :size-24)]
[rn/view
{:on-layout on-layout}
[hole-view/hole-view
{:holes (if options
[{:x (- @container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[rn/image {:style (style/collectible-img size) :source collectible-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
collectible-name]
[text/text
{:size :paragraph-2
:weight :medium
:margin-left 5
:style (style/label theme)}
collectible-id]]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])]))))
[{:keys [options blur? collectible-img-src collectible-name collectible-id] :as props}]
(let [theme (quo.theme/use-theme-value)
[container-width
set-container-width] (rn/use-state 0)
on-layout (rn/use-callback
#(set-container-width (oops/oget % :nativeEvent :layout :width)))
size (or (:size props) :size-24)]
[rn/view {:on-layout on-layout}
[hole-view/hole-view
{:holes (if options
[{:x (- container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[rn/image {:style (style/collectible-img size) :source collectible-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
collectible-name]
(when collectible-id
[text/text
{:size :paragraph-2
:weight :medium
:margin-left 5
:style (style/label theme)}
collectible-id])]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
+118 -115
View File
@@ -50,128 +50,131 @@
- `fade-end-percentage` Percentage where fading starts relative to the total
layout width of the `flat-list` data."
[{:keys [default-active fade-end-percentage]
:or {fade-end-percentage 0.8}}]
(let [active-tab-id (reagent/atom default-active)
fading (reagent/atom {:fade-end-percentage fade-end-percentage})
flat-list-ref (atom nil)]
(fn
[{:keys [data
fade-end-percentage
fade-end?
on-change
on-scroll
scroll-event-throttle
scrollable?
scroll-on-press?
size
type
labelled?
disabled?
blurred?
icon-color]
:or {fade-end-percentage fade-end-percentage
fade-end? false
scroll-event-throttle 64
scrollable? false
scroll-on-press? false
size default-tab-size}
:as props}]
(let [maybe-mask-wrapper (if fade-end?
[{:keys [default-active data fade-end-percentage fade-end? on-change on-scroll scroll-event-throttle
scrollable?
scroll-on-press? size type labelled? disabled? blurred? icon-color]
:or {fade-end-percentage 0.8
fade-end? false
scroll-event-throttle 64
scrollable? false
scroll-on-press? false
size default-tab-size}
:as props}]
(let [[active-tab-id
set-active-tab-id] (rn/use-state default-active)
[fading set-fading] (rn/use-state {:fade-end-percentage fade-end-percentage})
flat-list-ref (rn/use-ref-atom nil)
maybe-mask-wrapper (rn/use-memo
(fn []
(if fade-end?
[masked-view/masked-view
{:mask-element (reagent/as-element
[linear-gradient/linear-gradient
{:colors [:black :transparent]
:locations [(get @fading :fade-end-percentage)
:locations [(get fading :fade-end-percentage)
1]
:start {:x 0 :y 0}
:end {:x 1 :y 0}
:pointer-events :none
:style {:width "100%"
:height "100%"}}])}]
[:<>])]
(if scrollable?
(conj
maybe-mask-wrapper
[rn/flat-list
(merge
(dissoc props
:default-active
:fade-end-percentage
:fade-end?
:on-change
:scroll-on-press?
:size)
(when scroll-on-press?
{:initial-scroll-index (utils.collection/first-index #(= @active-tab-id (:id %)) data)})
{:ref #(reset! flat-list-ref %)
:extra-data (str @active-tab-id)
:horizontal true
:scroll-event-throttle scroll-event-throttle
:shows-horizontal-scroll-indicator false
:data data
:key-fn (comp str :id)
:on-scroll (fn [^js e]
(when fade-end?
(let [offset-x (oget e "nativeEvent.contentOffset.x")
content-width (oget e "nativeEvent.contentSize.width")
layout-width (oget e "nativeEvent.layoutMeasurement.width")
new-percentage (calculate-fade-end-percentage
{:offset-x offset-x
:content-width content-width
:layout-width layout-width
:max-fade-percentage fade-end-percentage})]
;; Avoid unnecessary re-rendering.
(when (not= new-percentage (get @fading :fade-end-percentage))
(swap! fading assoc :fade-end-percentage new-percentage))))
(when on-scroll
(on-scroll e)))
:render-fn (fn [{:keys [id label resource]} index]
[rn/view
{:style {:margin-right (if (= size default-tab-size) 12 8)
:padding-right (when (= index (dec (count data)))
(get-in props [:style :padding-left]))}}
[tag/tag
{:id id
:size size
:active (= id @active-tab-id)
:resource resource
:blurred? blurred?
:icon-color icon-color
:disabled? disabled?
:label (if labelled?
label
(when (= type :label) label))
:type type
:labelled? labelled?
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex ^js @flat-list-ref
#js
{:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}]])})])
[rn/view {:style {:flex-direction :row}}
(for [{:keys [id label resource]} data]
^{:key id}
[rn/view {:style {:margin-right 8}}
[tag/tag
(merge {:id id
:size size
:type type
:label (if labelled?
label
(when (= type :label) label))
:active (= id active-tab-id)
:disabled? disabled?
:blurred? blurred?
:icon-color icon-color
:labelled? (if (= type :label) true labelled?)
:resource (if (= type :icon)
:i/placeholder
resource)
:on-press #(do (reset! active-tab-id %)
(when on-change (on-change %)))})]])])))))
[:<>]))
[fade-end? fading])
on-scroll (rn/use-context
(fn [^js e]
(when fade-end?
(let [offset-x (oget e "nativeEvent.contentOffset.x")
content-width (oget e "nativeEvent.contentSize.width")
layout-width (oget e "nativeEvent.layoutMeasurement.width")
new-percentage (calculate-fade-end-percentage
{:offset-x offset-x
:content-width content-width
:layout-width layout-width
:max-fade-percentage fade-end-percentage})]
;; Avoid unnecessary re-rendering.
(when (not= new-percentage (get fading :fade-end-percentage))
(set-fading (assoc fading :fade-end-percentage new-percentage)))))
(when on-scroll
(on-scroll e)))
[fade-end? fading fade-end-percentage])
data-count (count data)
style-padding-left (get-in props [:style :padding-left])
render-fn (rn/use-callback
(fn [{:keys [id label resource]} index]
[rn/view
{:style {:margin-right (if (= size default-tab-size) 12 8)
:padding-right (when (= index (dec data-count))
style-padding-left)}}
[tag/tag
{:id id
:size size
:active (= id active-tab-id)
:resource resource
:blurred? blurred?
:icon-color icon-color
:disabled? disabled?
:label (if labelled?
label
(when (= type :label) label))
:type type
:labelled? labelled?
:on-press (fn [id]
(set-active-tab-id id)
(when scroll-on-press?
(.scrollToIndex ^js @flat-list-ref
#js
{:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}]])
[size default-tab-size data-count style-padding-left active-tab-id blurred?
icon-color disabled? labelled? type scroll-on-press? on-change])
on-press (rn/use-callback #(do (set-active-tab-id %) (when on-change (on-change %)))
[on-change])
key-fn (rn/use-callback (comp str :id))
ref (rn/use-callback #(reset! flat-list-ref %))
clean-props (dissoc props
:default-active
:fade-end-percentage
:fade-end?
:on-change
:scroll-on-press?
:size)]
(if scrollable?
(conj
maybe-mask-wrapper
[rn/flat-list
(merge
clean-props
(when scroll-on-press?
{:initial-scroll-index (utils.collection/first-index #(= active-tab-id (:id %)) data)})
{:ref ref
:extra-data (str active-tab-id)
:horizontal true
:scroll-event-throttle scroll-event-throttle
:shows-horizontal-scroll-indicator false
:data data
:key-fn key-fn
:on-scroll on-scroll
:render-fn render-fn})])
[rn/view {:style {:flex-direction :row}}
(for [{:keys [id label resource]} data]
^{:key id}
[rn/view {:style {:margin-right 8}}
[tag/tag
{:id id
:size size
:type type
:label (if labelled?
label
(when (= type :label) label))
:active (= id active-tab-id)
:disabled? disabled?
:blurred? blurred?
:icon-color icon-color
:labelled? (if (= type :label) true labelled?)
:resource (if (= type :icon) :i/placeholder resource)
:on-press on-press}]])])))
+43 -45
View File
@@ -7,10 +7,9 @@
[quo.components.utilities.token.view :as token]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
[reagent.core :as reagent]))
[react-native.hole-view :as hole-view]))
(defn- view-internal
(defn view
"Options:
- :options - false / :add / :hold (default false)
- :size - :size-24 / :size-32 (default :size-24)
@@ -18,45 +17,44 @@
- :theme - :light / :dark
- :token-value - string - token value
- :token-symbol - string"
[]
(let [container-width (reagent/atom 0)]
(fn [{:keys [options size blur? theme token-value token-img-src token-symbol]
:or {size :size-24}}]
[rn/view
{:on-layout #(reset! container-width
(oget % :nativeEvent :layout :width))}
[hole-view/hole-view
{:holes (if options
[{:x (- @container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[token/view
{:style (style/token-img size)
:token token-symbol
:size (case size
:size-24 :size-20
:size-32 :size-28)
:image-source token-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
(str token-value " " token-symbol)]]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])])))
(def view (quo.theme/with-theme view-internal))
[{:keys [options size blur? token-value token-img-src token-symbol]
:or {size :size-24}}]
(let [theme (quo.theme/use-theme-value)
[container-width
set-container-width] (rn/use-state 0)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view {:on-layout on-layout}
[hole-view/hole-view
{:holes (if options
[{:x (- container-width
(case size
:size-24 10
:size-32 12
nil))
:y (case size
:size-24 -6
:size-32 -4
nil)
:width 16
:height 16
:borderRadius 8}]
[])}
[rn/view {:style (style/container size options blur? theme)}
[token/view
{:style (style/token-img size)
:token token-symbol
:size (case size
:size-24 :size-20
:size-32 :size-28)
:image-source token-img-src}]
[text/text
{:size :paragraph-2
:weight :medium
:style (style/label theme)}
(str token-value " " token-symbol)]]]
(when options
[rn/view {:style (style/options-icon size)}
[icons/icon (if (= options :hold) :i/hold :i/add-token)
{:size 20
:no-color true}]])]))
@@ -4,7 +4,6 @@
[:map
[:type {:optional true} [:enum :default :watch-only :add-account :empty :missing-keypair]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:theme :schema.common/theme]
[:metrics? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]])
@@ -11,7 +11,6 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- loading-view
@@ -96,87 +95,87 @@
:end {:x 1 :y 0}}])
(defn- user-account
[_]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)]
(fn [{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
theme on-press]}]
(let [watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? @pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? @pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))))
[{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
on-press]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))
watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))
(defn- add-account-view
[_]
(let [pressed? (reagent/atom false)]
(fn [{:keys [on-press customization-color theme metrics?]}]
[rn/pressable
{:on-press on-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? @pressed?})}
[button/button
{:type :primary
:size 24
:icon true
:accessibility-label :add-account
:on-press on-press
:pressed? @pressed?
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:customization-color customization-color
:icon-only? true}
:i/add]])))
[{:keys [on-press customization-color metrics?]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? pressed?})}
[button/button
{:on-press on-press
:type :primary
:size 24
:icon true
:accessibility-label :add-account
:pressed? pressed?
:icon-only? true
:customization-color customization-color}
:i/add]]))
(defn- view-internal
[{:keys [type] :as props}]
@@ -185,6 +184,4 @@
:add-account [add-account-view props]
nil))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
@@ -6,9 +6,7 @@
[:props
[:map {:closed true}
[:status {:optional true} [:maybe [:enum :default :error]]]
[:theme :schema.common/theme]
[:on-inc-press {:optional true} [:maybe fn?]]
[:on-dec-press {:optional true} [:maybe fn?]]
[:on-change-text {:optional true} [:maybe fn?]]
[:container-style {:optional true} [:maybe :map]]
[:min-value {:optional true} [:maybe :int]]
[:max-value {:optional true} [:maybe :int]]
@@ -4,7 +4,7 @@
[quo.components.markdown.text :as text]
[quo.components.wallet.amount-input.schema :as amount-input.schema]
[quo.components.wallet.amount-input.style :as style]
[quo.theme :as quo.theme]
[quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
@@ -21,35 +21,34 @@
icon])
(defn- view-internal
[{:keys [on-inc-press on-dec-press theme status value min-value max-value
[{:keys [on-inc-press on-dec-press status value min-value max-value
container-style]
:or {value 0
min-value 0
max-value 999999999}}]
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]])
(let [theme (quo.theme/use-theme-value)]
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal amount-input.schema/?schema)))
(def view (schema/instrument #'view-internal amount-input.schema/?schema))
+4 -5
View File
@@ -37,8 +37,9 @@
:border? true}]))
(defn title-view
[{:keys [details action selected? type blur? customization-color on-options-press theme]}]
(let [{:keys [full-name]} details]
[{:keys [details action selected? type blur? customization-color on-options-press]}]
(let [theme (quo.theme/use-theme-value)
{:keys [full-name]} details]
[rn/view
{:style style/title-container
:accessibility-label :title}
@@ -89,7 +90,7 @@
[item & _rest]
[account-list-card/view item])
(defn- view-internal
(defn view
[{:keys [accounts action container-style selected? on-press] :as props}]
[rn/pressable
{:style (style/container (merge props
@@ -108,5 +109,3 @@
:render-fn acc-list-card
:separator [rn/view {:style {:height 8}}]
:style {:padding-horizontal 8}}]])
(def view (quo.theme/with-theme view-internal))
@@ -0,0 +1,23 @@
(ns quo.components.wallet.network-link.helpers)
(def ^:private central-figure-width 63)
(defn calculate-side-lines-path-1x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 57 L" side-offset " 57")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))
(defn calculate-transform
"Calculates the transform attribute for the central figure based on the SVG width."
[width]
(let [translate-x (/ (- width central-figure-width) 2)]
(str "translate(" translate-x " 0)")))
(defn calculate-side-lines-path-2x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 113 L" side-offset " 113")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))
@@ -0,0 +1,34 @@
(ns quo.components.wallet.network-link.style)
(def left-circle-container
{:position :absolute
:left -3})
(def right-circle-container
{:position :absolute
:right -3})
(def bottom-left-circle-container
{:position :absolute
:bottom -3
:left -3})
(def top-right-circle-container
{:position :absolute
:top -3
:right -3})
(def link-linear-container
{:flex-direction :row
:align-items :center
:height 10})
(def link-1x-container
{:flex 1
:height 58
:justify-content :center})
(def link-2x-container
{:flex 1
:height 114
:justify-content :center})
+131 -62
View File
@@ -1,82 +1,151 @@
(ns quo.components.wallet.network-link.view
(:require
[oops.core :refer [oget]]
[quo.components.wallet.network-link.helpers :as helpers]
[quo.components.wallet.network-link.schema :as component-schema]
[quo.components.wallet.network-link.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]
[schema.core :as schema]))
(defn- circle
[fill stroke]
[svg/svg
{:height "8"
:width "8"}
[svg/circle
{:cx "4"
:cy "4"
:r "3.5"
:fill fill
:stroke stroke
:strokeWidth "1"}]])
(defn- line
[stroke width]
[svg/svg
{:height "10"
:width "100%"
:view-box (str "0 0 " width " 10")}
[svg/path
{:d (str "M0,5 L" width ",5")
:stroke stroke
:stroke-width "1"}]])
(defn link-linear
[{:keys [source theme]}]
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "10" :fill :none}
[svg/path {:stroke (colors/resolve-color source theme) :d "M68 5H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/circle
{:cx "5"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]])
(let [[container-width
set-container-width] (rn/use-state 100)
stroke-color (colors/resolve-color source theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view
{:style style/link-linear-container
:on-layout on-layout}
[line stroke-color container-width]
[rn/view {:style style/left-circle-container}
[circle fill-color stroke-color]]
[rn/view {:style style/right-circle-container}
[circle fill-color stroke-color]]]))
(defn link-1x
[{:keys [source destination theme]}]
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "66" :fill :none}
[svg/path
{:stroke "url(#gradient)" :d "M68 5h-9.364c-11.046 0-20 8.954-20 20v16c0 11.046-8.955 20-20 20H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "61"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient" :x1 "72.271" :x2 "82.385" :y1 "5" :y2 "34.155" :gradientUnits "userSpaceOnUse"}
[svg/stop {:stopColor (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stopColor (colors/resolve-color source theme)}]]]])
(let [[container-width
set-container-width] (rn/use-state 100)
stroke-color "url(#gradient)"
source-color (colors/resolve-color source theme)
destination-color (colors/resolve-color destination theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
view-box (str "0 0 " container-width " 58")
side-lines-path (helpers/calculate-side-lines-path-1x container-width)
central-transform (helpers/calculate-transform container-width)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view
{:style style/link-1x-container
:on-layout on-layout}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M63 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 37C33.6356 48.0457 24.6813 57 13.6356 57L2.85889e-05 57"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.271"
:x2 "82.385"
:y1 "5"
:y2 "34.155"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))
(defn link-2x
[{:keys [source destination theme]}]
[svg/svg
{:width "73" :height "122" :viewBox "0 0 73 122" :fill "none" :xmlns "http://www.w3.org/2000/svg"}
[svg/path
{:d
"M67.9999 5L58.6356 5C47.5899 5 38.6356 13.9543 38.6356 25L38.6356 97C38.6356 108.046 29.6813 117 18.6356 117L5.00006 117"
:stroke "url(#gradient)"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "117"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradientUnits "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]])
(let [[container-width
set-container-width] (rn/use-state 100)
stroke-color "url(#gradient)"
source-color (colors/resolve-color source theme)
destination-color (colors/resolve-color destination theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
view-box (str "0 0 " container-width " 114")
side-lines-path (helpers/calculate-side-lines-path-2x container-width)
central-transform (helpers/calculate-transform container-width)
on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))]
[rn/view
{:style style/link-2x-container
:on-layout on-layout}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M62.9999 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 93C33.6356 104.046 24.6813 113 13.6356 113L5.71778e-05 113"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))
(defn- view-internal
[{:keys [shape container-style] :as props}]
@@ -12,6 +12,5 @@
[:amount :int]
[:max-amount :int]
[:network-name [:or :string :keyword]]]]]]
[:container-style {:optional true} [:maybe :map]]
[:theme :schema.common/theme]]]]
[:container-style {:optional true} [:maybe :map]]]]]
:any])
@@ -29,13 +29,13 @@
:background-color (colors/resolve-color network-name nil 10)})
(defn network-bar
[{:keys [max-width on-top? bar-division? theme]
[{:keys [bar-max-width on-top? bar-division? theme]
{:keys [network-name translate-x-shared-value]} :bar}
width-shared-value]
(reanimated/apply-animations-to-style
{:width width-shared-value
:transform [{:translate-x translate-x-shared-value}]}
{:max-width max-width
{:max-width bar-max-width
:flex-direction :row
:justify-content :flex-end
:background-color (colors/resolve-color network-name nil)
@@ -8,7 +8,6 @@
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[schema.core :as schema]
[utils.number]))
@@ -22,60 +21,73 @@
(js/clearTimeout (k @timeouts))
(swap! timeouts assoc k (js/setTimeout exec-fn-and-remove-timeout ms))))
(defn- f-slider
[slider-shared-values]
[rn/view {:style style/slider-container}
[reanimated/view {:style (style/slider slider-shared-values)}]])
(def get-gesture
(memoize
(fn
[[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture]]
(-> (gesture/gesture-pan)
(gesture/enabled detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(set-detecting-gesture false) 20)))))))
(defn f-network-bar
[_]
(let [detecting-gesture? (reagent/atom false)
amount-on-gesture-start (atom 0)]
(fn [{:keys [total-width total-amount on-press on-new-amount allow-press?]
{:keys [amount-shared-value
max-amount]} :bar
:as props}]
(let [slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)]
[rn/pressable
{:on-press (fn []
(when (and (not @detecting-gesture?) allow-press?)
(on-press)
(reset! detecting-gesture? true)
(animation/show-slider slider-opacity-shared-value)))}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector
{:gesture
(-> (gesture/gesture-pan)
(gesture/enabled @detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(reset! detecting-gesture? false) 20))))}
[:f> f-slider
{:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value}]]]]))))
(defn network-bar
[{:keys [total-width total-amount on-press on-new-amount allow-press?
bar-idx bar-width bar-max-width]
{:keys [amount-shared-value
max-amount]
:as bar} :bar
:as props}]
(let [[detecting-gesture?
set-detecting-gesture] (rn/use-state false)
amount-on-gesture-start (rn/use-ref-atom 0)
slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)
gesture (get-gesture
[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture])
on-press (rn/use-callback
(fn []
(when (and (not detecting-gesture?) allow-press?)
(on-press bar bar-idx bar-width bar-max-width)
(set-detecting-gesture true)
(animation/show-slider slider-opacity-shared-value)))
[detecting-gesture? allow-press? on-press])]
[rn/pressable
{:on-press on-press}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector {:gesture gesture}
[rn/view {:style style/slider-container}
[reanimated/view
{:style (style/slider {:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value})}]]]]]))
(defn- add-bar-shared-values
[{:keys [amount] :as network}]
@@ -93,101 +105,113 @@
(interleave (repeat [rn/view {:style (style/dashed-line-line network-name)}])
(repeat [rn/view {:style style/dashed-line-space}])))))
(defn f-network-routing-bars
[_]
(let [selected-network-idx (reagent/atom nil)
press-locked? (reagent/atom false)
lock-press #(reset! press-locked? true)
unlock-press #(reset! press-locked? false)
reset-state-values #(reset! selected-network-idx nil)]
(fn [{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))]
(rn/use-effect
#(when (and (not requesting-data?) @selected-network-idx)
(let [bar (nth network-bars @selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) @selected-network-idx)
this-bar-selected? (= @selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[:f> f-network-bar
{:bar bar
:max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not @selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not @press-locked?))
:on-press (fn []
(when-not @selected-network-idx
(let [[previous-bars [_ & next-bars]] (split-at bar-idx network-bars)
number-previous-bars bar-idx]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars number-previous-bars})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc number-previous-bars)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(reset! selected-network-idx bar-idx)))
:on-new-amount (fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount @selected-network-idx)))}]))
(defn- network-routing-bars
[{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [[selected-network-idx
set-selected-network-idx] (rn/use-state nil)
[press-locked?
set-press-locked] (rn/use-state false)
lock-press (rn/use-callback #(set-press-locked true))
unlock-press (rn/use-callback #(set-press-locked false))
reset-state-values (rn/use-callback #(set-selected-network-idx nil))
bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))
network-bar-on-press (rn/use-callback
(fn [bar bar-idx bar-width bar-max-width]
(when-not selected-network-idx
(let [[previous-bars
[_ & next-bars]] (split-at bar-idx network-bars)]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars bar-idx})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc bar-idx)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(set-selected-network-idx bar-idx)))
[selected-network-idx bars-widths-negative network-bars])
on-new-amount (rn/use-callback
(fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount selected-network-idx)))
[on-amount-selected selected-network-idx])]
(rn/use-effect
#(when (and (not requesting-data?) selected-network-idx)
(let [bar (nth network-bars selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) selected-network-idx)
this-bar-selected? (= selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[network-bar
{:bar bar
:bar-idx bar-idx
:bar-width bar-width
:bar-max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not press-locked?))
:on-press network-bar-on-press
:on-new-amount on-new-amount}]))
(let [{:keys [max-amount network-name]} (some->> @selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))))
(let [{:keys [max-amount network-name]} (some->> selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))
(defn view-internal
[{:keys [networks container-style theme] :as params}]
(reagent/with-let [total-width (reagent/atom nil)]
[{:keys [networks container-style] :as params}]
(let [theme (quo.theme/use-theme-value)
[total-width
set-total-width] (rn/use-state nil)
on-layout (rn/use-callback #(let [width (oops/oget % "nativeEvent.layout.width")]
(when (not= width total-width)
(set-total-width width))))]
(rn/use-unmount (fn []
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout))))
[rn/view
{:accessibility-label :network-routing
:style (style/container container-style theme)
:on-layout #(reset! total-width (oops/oget % "nativeEvent.layout.width"))}
(when @total-width
:on-layout on-layout}
(when total-width
^{:key (str "network-routing-" (count networks))}
[:f> f-network-routing-bars (assoc params :total-width @total-width)])]
(finally
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout)))))
[network-routing-bars (assoc params :total-width total-width)])]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal network-routing-schema/?schema)))
(def view (schema/instrument #'view-internal network-routing-schema/?schema))
@@ -14,6 +14,5 @@
[:networks {:optional true}
[:maybe [:sequential [:map [:source [:maybe :schema.common/image-source]]]]]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:value {:optional true} [:maybe :string]]
[:theme :schema.common/theme]]]]
[:value {:optional true} [:maybe :string]]]]]
:any])
+37 -35
View File
@@ -12,7 +12,6 @@
[quo.foundations.common :as common]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn fiat-format
@@ -75,7 +74,8 @@
[token-name-text theme text]])
(defn input-section
[{:keys [on-change-text value value-atom on-selection-change on-token-press]}]
[{:keys [on-change-text value value-internal set-value-internal on-selection-change
on-token-press]}]
(let [input-ref (atom nil)
set-ref #(reset! input-ref %)
focus-input #(when-let [ref ^js @input-ref]
@@ -83,7 +83,7 @@
controlled-input? (some? value)
handle-on-change-text (fn [v]
(when-not controlled-input?
(reset! value-atom v))
(set-value-internal v))
(when on-change-text
(on-change-text v)))
handle-selection-change (fn [^js e]
@@ -117,41 +117,43 @@
:on-selection-change handle-selection-change
:selection (clj->js selection)}
controlled-input? (assoc :value value)
(not controlled-input?) (assoc :default-value @value-atom))]]
(not controlled-input?) (assoc :default-value value-internal))]]
[token-label
{:theme theme
:text (if crypto? token currency)
:value (if controlled-input? value @value-atom)}]])))
:value (if controlled-input? value value-internal)}]])))
(defn- view-internal
[]
(let [width (:width (rn/get-window))
value-atom (reagent/atom nil)
crypto? (reagent/atom true)]
(fn [{:keys [theme container-style value on-swap] :as props}]
(let [handle-on-swap (fn []
(swap! crypto? not)
(when on-swap (on-swap @crypto?)))]
[rn/view {:style (merge (style/main-container width) container-style)}
[rn/view {:style style/amount-container}
[input-section
(assoc props
:value-atom value-atom
:crypto? @crypto?)]
[button/button
{:icon true
:icon-only? true
:size 32
:on-press handle-on-swap
:type :outline
:accessibility-label :reorder}
:i/reorder]]
[divider-line/view {:container-style (style/divider theme)}]
[data-info
(assoc props
:crypto? @crypto?
:amount (or value @value-atom))]]))))
[{:keys [container-style value on-swap] :as props}]
(let [theme (quo.theme/use-theme-value)
width (:width (rn/get-window))
[value-internal set-value-internal] (rn/use-state nil)
[crypto? set-crypto] (rn/use-state true)
handle-on-swap (rn/use-callback
(fn []
(set-crypto (not crypto?))
(when on-swap (on-swap (not crypto?))))
[crypto? on-swap])]
[rn/view {:style (merge (style/main-container width) container-style)}
[rn/view {:style style/amount-container}
[input-section
(assoc props
:value-internal value-internal
:set-value-internal set-value-internal
:crypto? crypto?)]
[button/button
{:icon true
:icon-only? true
:size 32
:on-press handle-on-swap
:type :outline
:accessibility-label :reorder}
:i/reorder]]
[divider-line/view {:container-style (style/divider theme)}]
[data-info
(assoc props
:theme theme
:crypto? crypto?
:amount (or value value-internal))]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
@@ -13,7 +13,6 @@
[:blur? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]
[:state {:optional true} [:maybe [:= :disabled]]]
[:theme :schema.common/theme]
[:second-tag-prefix {:optional true} [:maybe :keyword]]
[:third-tag-prefix {:optional true} [:maybe :keyword]]
[:fourth-tag-prefix {:optional true} [:maybe :keyword]]
@@ -8,7 +8,6 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
[reagent.core :as reagent]
[schema.core :as schema]
[utils.i18n :as i18n]))
@@ -100,39 +99,37 @@
[context-tag/view (merge props {:size 24 :blur? blur?})]])
(defn- view-internal
[_]
(let [pressed? (reagent/atom false)]
(fn
[{:keys [state theme blur?
on-press
first-tag second-tag third-tag fourth-tag
second-tag-prefix third-tag-prefix fourth-tag-prefix]
:as props}]
[rn/pressable
{:style (style/wallet-activity-container {:pressed? @pressed?
:theme theme
:blur? blur?})
:accessibility-label :wallet-activity
:disabled (= state :disabled)
:on-press on-press
:on-press-in (fn [] (reset! pressed? true))
:on-press-out (fn [] (reset! pressed? false))}
[rn/view
{:style {:flex-direction :row}}
[transaction-icon-view props]
[rn/view
{:style style/content-container}
[transaction-header props]
[rn/view {:style style/content-line}
(when first-tag [prop-tag first-tag blur?])
(when second-tag-prefix [prop-text second-tag-prefix theme])
(when second-tag [prop-tag second-tag blur?])]
[rn/view {:style style/content-line}
(when third-tag-prefix [prop-text third-tag-prefix theme])
(when third-tag [prop-tag third-tag blur?])
(when fourth-tag-prefix [prop-text fourth-tag-prefix theme])
(when fourth-tag [prop-tag fourth-tag blur?])]]]])))
[{:keys [state blur? first-tag second-tag third-tag fourth-tag on-press
second-tag-prefix third-tag-prefix fourth-tag-prefix]
:as props}]
(let [theme (quo.theme/use-theme-value)
[pressed?
set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:style (style/wallet-activity-container {:pressed? pressed?
:theme theme
:blur? blur?})
:accessibility-label :wallet-activity
:disabled (= state :disabled)
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out}
[rn/view
{:style {:flex-direction :row}}
[transaction-icon-view props]
[rn/view
{:style style/content-container}
[transaction-header props]
[rn/view {:style style/content-line}
(when first-tag [prop-tag first-tag blur?])
(when second-tag-prefix [prop-text second-tag-prefix theme])
(when second-tag [prop-tag second-tag blur?])]
[rn/view {:style style/content-line}
(when third-tag-prefix [prop-text third-tag-prefix theme])
(when third-tag [prop-tag third-tag blur?])
(when fourth-tag-prefix [prop-text fourth-tag-prefix theme])
(when fourth-tag [prop-tag fourth-tag blur?])]]]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
+2
View File
@@ -32,6 +32,7 @@
quo.components.community.channel-action.view
quo.components.community.channel-actions.view
quo.components.community.community-card-view
quo.components.community.community-detail-token-gating.view
quo.components.community.community-list-view
quo.components.community.community-stat.view
quo.components.community.community-view
@@ -224,6 +225,7 @@
;;;; Community
(def community-card-view-item quo.components.community.community-card-view/view)
(def community-detail-token-gating quo.components.community.community-detail-token-gating.view/view)
(def communities-membership-list-item
quo.components.community.community-list-view/communities-membership-list-item)
(def community-stats-column quo.components.community.community-view/community-stats-column)
+1
View File
@@ -16,6 +16,7 @@
quo.components.calendar.calendar.component-spec
quo.components.calendar.calendar.month-picker.component-spec
quo.components.colors.color-picker.component-spec
quo.components.community.community-detail-token-gating.component-spec
quo.components.community.community-stat.component-spec
quo.components.counter.collectible-counter.component-spec
quo.components.counter.counter.component-spec
+4 -1
View File
@@ -10,7 +10,10 @@
(def ui-themed
{:no-funds
{:light (js/require "../resources/images/ui2/no-funds-light.png")
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}})
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}
:no-contacts-to-chat
{:light (js/require "../resources/images/ui2/no-contacts-to-chat-light.png")
:dark (js/require "../resources/images/ui2/no-contacts-to-chat-dark.png")}})
(defn get-themed-image
[k theme]
-4
View File
@@ -226,7 +226,3 @@
(defn open-url [link] (.openURL ^js linking link))
(def set-status-bar-style react-native/StatusBar.setBarStyle)
(defn sharing
[content]
(.share (.-Share ^js react-native) (clj->js content)))
+1 -5
View File
@@ -4,9 +4,5 @@
(defn open
([options]
(open options #() #()))
([options on-success on-error]
(-> ^js react-native-share
(.open (clj->js options))
(.then on-success)
(.catch on-error))))
(.open (clj->js options)))))
+1 -1
View File
@@ -12,5 +12,5 @@
(defn contacts-section-header
[{:keys [title]}]
(let [theme (quo.theme/use-theme-value)]
[quo/divider-label {:container-style {:background-color (style/contacts-section-header theme)}}
[quo/divider-label {:container-style (style/contacts-section-header theme)}
title]))
@@ -5,7 +5,7 @@
[utils.re-frame :as rf]))
(defn contact-list-item
[{:keys [on-press on-long-press accessory allow-multiple-presses?]}
[{:keys [on-press on-long-press accessory allow-multiple-presses? disabled?]}
{:keys [primary-name secondary-name public-key compressed-key ens-verified added?]}
theme]
(let [photo-path (rf/sub [:chats/photo-path public-key])
@@ -24,4 +24,5 @@
:contact? added?
:on-press on-press
:on-long-press on-long-press
:accessory accessory}]))
:accessory accessory
:disabled? disabled?}]))
+13 -10
View File
@@ -40,13 +40,15 @@
params: sequence - A positional sequence of zero or more arguments.
on-success/on-error: function/vector (optional) - When a function, it will be
called with the transformed response as the sole argument. When a vector, it
is expected to be a valid re-frame event vector, and the event will be
dispatched with the transformed response conj'ed at the end.
called with the transformed response and id (request-id) as the argument.
When a vector, it is expected to be a valid re-frame event vector, and the event
will be dispatched with the transformed response and id conj'ed at the end.
js-response: boolean - When non-nil, the successful response will not be
recursively converted to Clojure data structures. Default: nil.
id: (optional) - id passed while making the RPC call will be returned in response
number-of-retries: integer - The maximum number of retries in case of failure.
Default: nil.
@@ -56,14 +58,14 @@
Note that on-error is optional, but if not provided, a default implementation
will be used.
"
[{:keys [method params on-success on-error js-response] :as arg}]
[{:keys [method params on-success on-error js-response id] :as arg}]
(let [params (or params [])
on-error (or on-error
(on-error-retry call arg)
#(log/warn :json-rpc/error method :error % :params params))]
(native-module/call-private-rpc
(transforms/clj->json {:jsonrpc "2.0"
:id 1
:id (or id 1)
:method method
:params params})
(fn [raw-response]
@@ -79,12 +81,13 @@
(rf/dispatch (conj on-error error))
(on-error error)))
(when on-success
(let [result (if js-response
(.-result response-js)
(transforms/js->clj (.-result response-js)))]
(let [result (if js-response
(.-result response-js)
(transforms/js->clj (.-result response-js)))
request-id (.-id response-js)]
(if (vector? on-success)
(rf/dispatch (conj on-success result))
(on-success result)))))))))))
(rf/dispatch (conj on-success result request-id))
(on-success result request-id)))))))))))
(re-frame/reg-fx
:json-rpc/call
+6 -5
View File
@@ -3,7 +3,6 @@
[react-native.cameraroll :as cameraroll]
[react-native.fs :as fs]
[react-native.platform :as platform]
[react-native.share :as share]
[utils.re-frame :as rf]))
(def config
@@ -15,10 +14,12 @@
(blob/fetch uri
config
(fn [downloaded-url]
(share/open {:url (str (when platform/android? "file://") downloaded-url)
:isNewTask true}
#(fs/unlink downloaded-url)
#(fs/unlink downloaded-url))))))
(rf/dispatch [:open-share
{:options {:url (str (when platform/android? "file://")
downloaded-url)
:isNewTask true}
:on-success #(fs/unlink downloaded-url)
:on-error #(fs/unlink downloaded-url)}])))))
(rf/reg-fx :effects.lightbox/save-image-to-gallery
(fn [[uri on-success]]
+16 -4
View File
@@ -100,7 +100,7 @@
ens?
(assoc-in [:route-params :ens-name] fragment)
(and (or (= handler :community) (= handler :community-chat)) compressed-key?)
(and (= handler :community) compressed-key?)
(assoc-in [:route-params :community-id] fragment)
(and equal-end-of-base64url (= handler :community) (:community-data route-params))
@@ -119,6 +119,13 @@
(re-find constants/regx-starts-with-uuid (:community-data route-params)))
(assoc-in [:route-params :community-channel-id] (:community-data route-params))
(and fragment
(= handler :community-chat)
(:community-data route-params)
(string? (:community-data route-params))
(not (re-find constants/regx-starts-with-uuid (:community-data route-params))))
(assoc-in [:route-params :community-encoded-data?] true)
(and equal-end-of-base64url (= handler :user) (:user-data route-params))
(update-in [:route-params :user-data] #(str % equal-end-of-base64url))
@@ -203,6 +210,10 @@
(cb {:type :private-chat
:error :invalid-chat-id})))))
(defn parse-shared-url
[uri]
(re-frame/dispatch [:shared-urls/parse-shared-url uri]))
(defn match-community-channel-async
[{:keys [community-channel-id community-id]} cb]
(if (validators/valid-compressed-key? community-id)
@@ -307,9 +318,10 @@
(and (= handler :community-chat) (:community-channel-id route-params) (:community-id route-params))
(match-community-channel-async route-params cb)
(and (= handler :community-chat) (:community-id route-params))
(cb {:type :community
:community-id (:community-id route-params)})
(and (= handler :community-chat)
(:community-encoded-data? route-params)
(:community-id route-params))
(parse-shared-url uri)
;; NOTE: removed in `match-uri`, might need this in the future
(= handler :wallet-account)
+2
View File
@@ -42,12 +42,14 @@
[:community-chat
{:community-data
"G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM="
:community-encoded-data? true
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
"status-app://cc/G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
[:community-chat
{:community-data
"G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM="
:community-encoded-data? true
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
"https://status.app/cc/c432709e-fc73-440d-bb67-cb3a0929dfda#zQ3shZL6dXiFCbDyxnXxwQa9v8QFC2q19subFtyxd7kVszMVo"
@@ -0,0 +1,20 @@
(ns status-im.common.shared-urls.data-store
(:require
[clojure.set :as set]))
(defn <-rpc
[{:keys [community channel contact]}]
{:community (set/rename-keys
community
{:communityId :community-id
:displayName :display-name
:membersCount :members-count
:tagIndices :tag-indices})
:channel (set/rename-keys
channel
{:displayName :display-name
:channelUuid :channel-uuid})
:contact (set/rename-keys
contact
{:displayName :display-name
:publicKey :public-key})})
@@ -0,0 +1,33 @@
(ns status-im.common.shared-urls.events
(:require
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.common.shared-urls.data-store :as data-store]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(defn parse-shared-url-success
[url response]
(let [parsed-data (data-store/<-rpc response)
cb #(rf/dispatch [:universal-links/match-value url %])]
(cond
(:channel parsed-data)
(cb {:type :community-chat
:chat-id (str (get-in parsed-data [:community :community-id])
(get-in parsed-data [:channel :channel-uuid]))
:community-id (get-in parsed-data [:community :community-id])})
(:community parsed-data)
(cb {:type :community :community-id (get-in parsed-data [:community :community-id])}))))
(defn parse-shared-url
[_ [uri]]
(let [url (string/replace uri #"status-app:/" "https://status.app")]
{:json-rpc/call
[{:method "wakuext_parseSharedURL"
:params [url]
:on-success #(parse-shared-url-success url %)
:on-error (fn [err]
(log/error "failed to parse shared url" {:error err :url url}))}]}))
(re-frame/reg-event-fx :shared-urls/parse-shared-url parse-shared-url)
@@ -18,8 +18,8 @@
[rn/view
[quo/text
{:accessibility-label :sync-code-generated
:weight :bold
:size :heading-1
:weight :semi-bold
:size :heading-2
:style {:margin-bottom 4}}
(i18n/label :t/enter-password)]
[rn/view
+1 -1
View File
@@ -61,4 +61,4 @@
(->> (rf/sub [:toasts])
:ordered
(into [rn/view {:style (style/outmost-transparent-container)}]
(map #(with-meta [:f> f-container %] {:key %})))))
(map #(with-meta [f-container %] {:key %})))))
+7 -5
View File
@@ -116,14 +116,16 @@
(log/debug "universal-links: no url")))
(rf/defn on-handle
{:events [::match-value]}
[cofx url {:keys [type chat-id] :as data}]
{:events [:universal-links/match-value]}
[cofx url {:keys [type chat-id community-id] :as data}]
(case type
:group-chat (handle-group-chat cofx data)
:private-chat (handle-private-chat cofx data)
:community-requests (handle-community-requests cofx data)
:community (communities.events/navigate-to-serialized-community cofx data)
:community-chat {:dispatch [:communities/navigate-to-community-chat chat-id :pop-to-root]}
:community (communities.events/navigate-to-community-overview cofx [community-id])
:community-chat (communities.events/navigate-to-community-chat cofx
[chat-id :pop-to-root
community-id])
:contact (handle-view-profile cofx data)
:browser (handle-browse cofx data)
:eip681 (handle-eip681 cofx data)
@@ -136,7 +138,7 @@
{:router/handle-uri {:chain (chain/chain-keyword db)
:chats (:chats db)
:uri url
:cb #(re-frame/dispatch [::match-value url %])}})
:cb #(re-frame/dispatch [:universal-links/match-value url %])}})
(rf/defn store-url-for-later
"Store the url in the db to be processed on login"
+14
View File
@@ -2,6 +2,7 @@
(:require
[clojure.string :as string]
[react-native.config :as react-native-config]
[status-im.constants :as constants]
[utils.ens.core :as utils.ens]
[utils.ethereum.chain :as chain]))
@@ -59,6 +60,7 @@
(def swap-enabled? (enabled? (get-config :SWAP_ENABLED "0")))
(def stickers-test-enabled? (enabled? (get-config :STICKERS_TEST_ENABLED "0")))
(def local-pairing-mode-enabled? (enabled? (get-config :LOCAL_PAIRING_ENABLED "1")))
(def show-not-implemented-features? (enabled? (get-config :SHOW_NOT_IMPLEMENTED_FEATURES "0")))
;; CONFIG VALUES
(def log-level (string/upper-case (get-config :LOG_LEVEL "")))
@@ -69,6 +71,18 @@
; currently not supported in status-go
(def enable-remove-profile-picture? false)
(defn env-variable->int
[env-var-name default-value]
(js/parseInt (get-config env-var-name default-value)))
(def delete-message-for-me-undo-time-limit-ms
(env-variable->int :DELETE_MESSAGE_FOR_ME_UNDO_TIME_LIMIT
constants/delete-message-for-me-undo-time-limit-ms))
(def delete-message-undo-time-limit-ms
(env-variable->int :DELETE_MESSAGE_UNDO_TIME_LIMIT
constants/delete-message-undo-time-limit-ms))
(def verify-transaction-chain-id (js/parseInt (get-config :VERIFY_TRANSACTION_CHAIN_ID "1")))
(def verify-transaction-url
(if (= :mainnet (chain/chain-id->chain-keyword verify-transaction-chain-id))
+2
View File
@@ -448,6 +448,8 @@
(def ^:const optimism-short-name "opt")
(def ^:const arbitrum-short-name "arb1")
(def ^:const default-multichain-address-prefix "eth:opt:arb1:")
(def ^:const mainnet-abbreviated-name "Eth.")
(def ^:const optimism-abbreviated-name "Opt.")
(def ^:const arbitrum-abbreviated-name "Arb1.")
@@ -101,6 +101,7 @@
{:keys [primary-name public-key]} (when one-contact-selected?
(rf/sub [:contacts/contact-by-identity
(first selected-contacts)]))]
(rn/use-unmount #(rf/dispatch [:group-chat/clear-contacts]))
[rn/view {:flex 1}
[rn/view {:padding-horizontal 20}
[quo/button
+1 -2
View File
@@ -5,7 +5,6 @@
[re-frame.core :as re-frame]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.share :as share]
[status-im.common.contact-list-item.view :as contact-list-item]
[status-im.common.contact-list.view :as contact-list]
[status-im.common.home.actions.view :as actions]
@@ -122,7 +121,7 @@
[:show-bottom-sheet {:content chat.actions.view/new-chat}])
:accessibility-label :new-chat-button}
:card-props
{:on-press #(share/open {:url profile-link})
{:on-press #(rf/dispatch [:open-share {:options {:url profile-link}}])
:banner (resources/get-image :invite-friends)
:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}})
@@ -41,8 +41,13 @@
(defn f-send-button
[props state animations window-height images? btn-opacity z-index edit]
(let [{:keys [text-value]} state
customization-color (rf/sub [:profile/customization-color])]
(let [{:keys [text-value]} state
profile-customization-color (rf/sub [:profile/customization-color])
{:keys [chat-id chat-type]
chat-color :color} (rf/sub [:chats/current-chat-chat-view])
contact-customization-color (when (= chat-type constants/one-to-one-chat-type)
(rf/sub [:contacts/contact-customization-color-by-address
chat-id]))]
(rn/use-effect (fn []
;; Handle send button opacity animation and z-index when input content changes
(if (or (seq @text-value) images?)
@@ -60,7 +65,7 @@
[quo/button
{:icon-only? true
:size 32
:customization-color customization-color
:customization-color (or contact-customization-color chat-color profile-customization-color)
:accessibility-label :send-message-button
:on-press #(send-message props state animations window-height edit)}
:i/arrow-up]]))
@@ -8,13 +8,13 @@
:margin-bottom (when-not community? 12)})
(def community-tag-container
{:margin-horizontal 20 :margin-top 4 :margin-bottom 12})
{:margin-horizontal 20
:margin-top 4
:margin-bottom 12})
(def no-pinned-messages-container
{:justify-content :center
:align-items :center
:margin 12
:margin-bottom (when platform/android? 12)})
:align-items :center})
(def list-footer
{:height (when platform/android? 12)})
@@ -29,11 +29,15 @@
(defn pinned-message
[{:keys [from quoted-message timestamp-str]}]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
customization-color (rf/sub [:profile/customization-color])]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
one-to-one-chat? (rf/sub [:chats/current-chat-one-to-one?])
current-chat-color (rf/sub [:chats/current-chat-color])
contact-customization-color (rf/sub [:contacts/contact-customization-color-by-address from])]
[quo/system-message
{:type :pinned
:pinned-by primary-name
:customization-color customization-color
:customization-color (if one-to-one-chat?
contact-customization-color
current-chat-color)
:child [reply/quoted-message quoted-message false true]
:timestamp timestamp-str}]))
@@ -51,7 +51,7 @@
[rn/view {:padding-top 4 :width 32}]))
(defn author
[{:keys [response-to
[{:keys [content
compressed-key
last-in-group?
pinned-by
@@ -61,7 +61,7 @@
show-reactions?
in-reaction-and-action-menu?
show-user-info?]
(when (or (and (seq response-to) quoted-message)
(when (or (and (seq (:response-to content)) quoted-message)
last-in-group?
pinned-by
show-user-info?
@@ -79,16 +79,17 @@
(defn system-message-contact-request
[{:keys [chat-id timestamp-str from]} type]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity chat-id])
contact (rf/sub [:contacts/contact-by-address chat-id])
photo-path (when (seq (:images contact)) (rf/sub [:chats/photo-path chat-id]))
customization-color (rf/sub [:profile/customization-color])
public-key (rf/sub [:profile/public-key])]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity chat-id])
{:keys [images]
contact-customization-color
:customization-color} (rf/sub [:contacts/contact-by-address chat-id])
photo-path (when (seq images) (rf/sub [:chats/photo-path chat-id]))
public-key (rf/sub [:profile/public-key])]
[quo/system-message
{:type type
:timestamp timestamp-str
:display-name primary-name
:customization-color customization-color
:customization-color contact-customization-color
:photo-path photo-path
:incoming? (not= public-key from)}]))
@@ -7,6 +7,7 @@
[react-native.gesture :as gesture]
[reagent.core :as reagent]
[status-im.common.contact-list-item.view :as contact-list-item]
[status-im.config :as config]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.composer.reply.view :as reply]
[status-im.contexts.chat.messenger.messages.drawers.style :as style]
@@ -140,7 +141,7 @@
(rf/dispatch
[:hide-bottom-sheet])
(rf/dispatch [:chat.ui/delete-message-for-me message-data
constants/delete-message-for-me-undo-time-limit-ms]))
config/delete-message-for-me-undo-time-limit-ms]))
:label (i18n/label :t/delete-for-me)
:icon :i/delete
@@ -155,7 +156,7 @@
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:chat.ui/delete-message message-data
constants/delete-message-undo-time-limit-ms]))
config/delete-message-undo-time-limit-ms]))
:label (i18n/label :t/delete-for-everyone)
:icon :i/delete
:id :delete-for-all}])))
@@ -1,6 +1,8 @@
(ns status-im.contexts.chat.messenger.messages.list.style
(:require
[quo.foundations.colors :as colors]
[quo.foundations.shadows :as shadows]
[quo.theme :as quo.theme]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
@@ -23,10 +25,12 @@
[bottom theme top-margin]
(reanimated/apply-animations-to-style
{:bottom bottom}
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:padding-horizontal 20
:border-radius 20
:margin-top top-margin}))
(merge
(shadows/get 2 (quo.theme/get-theme) :inverted)
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:padding-horizontal 20
:border-radius 20
:margin-top top-margin})))
(defn header-image
[scale top left theme]
@@ -1,5 +1,6 @@
(ns status-im.contexts.chat.messenger.messages.list.view
(:require
[clojure.string :as string]
[legacy.status-im.ui.screens.chat.group :as chat.group]
[oops.core :as oops]
[quo.core :as quo]
@@ -112,19 +113,24 @@
[rn/view {:style {:height height}}]))
(defn list-footer-avatar
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color]}]
(let [scale (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[1 0.4]
messages.constants/default-extrapolation-option)
top (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[-44 -12]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[16 -8]
messages.constants/default-extrapolation-option)]
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color
emoji chat-type chat-name last-message]}]
(let [scale (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[1 0.4]
messages.constants/default-extrapolation-option)
top (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[-44 -12]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[16 -8]
messages.constants/default-extrapolation-option)
community-channel? (= chat-type constants/community-chat-type)]
[reanimated/view
{:style (style/header-image scale top left theme)}
(if group-chat
@@ -132,7 +138,10 @@
{:customization-color color
:size :size-80
:picture profile-picture
:override-theme :dark}]
:emoji (when (and (not (string/blank? emoji))
community-channel?)
(string/trim emoji))
:chat-name chat-name}]
[quo/user-avatar
{:full-name display-name
:online? online?
@@ -140,15 +149,17 @@
:size :big}])]))
(defn chat-display-name
[{:keys [distance-from-list-top display-name contact theme]}]
(let [top (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[0 -35]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[0 40]
messages.constants/default-extrapolation-option)]
[{:keys [distance-from-list-top display-name contact theme last-message]}]
(let [top (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[0 -35]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[0 40]
messages.constants/default-extrapolation-option)]
[reanimated/view
{:style (style/user-name-container top left)}
[rn/view
@@ -195,54 +206,55 @@
muted?)))}]}]))
(defn bio-and-actions
[{:keys [distance-from-list-top bio chat-id customization-color]}]
(let [has-bio (seq bio)
[{:keys [distance-from-list-top bio chat-id customization-color last-message description]}]
(let [has-bio (seq (or bio description))
top (reanimated/interpolate
distance-from-list-top
[0 messages.constants/header-container-top-margin]
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[(if has-bio 8 16) (if has-bio -28 -20)]
messages.constants/default-extrapolation-option)]
[reanimated/view
{:style (style/bio-and-actions top)}
(when has-bio
[quo/text bio])
[quo/text (or bio description)])
[actions chat-id customization-color]]))
(defn footer-component
[{:keys [chat distance-from-list-top theme customization-color]}]
(let [{:keys [chat-id chat-name emoji chat-type
group-chat color]} chat
display-name (cond
(= chat-type constants/one-to-one-chat-type)
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
(= chat-type constants/community-chat-type)
(str (when emoji (str emoji " ")) "# " chat-name)
:else (str emoji chat-name))
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
online? (rf/sub [:visibility-status-updates/online? chat-id])
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))
photo-path (rf/sub [:chats/photo-path chat-id])
top-margin (+ (safe-area/get-top)
messages.constants/top-bar-height
messages.constants/header-container-top-margin
32)
background-color (colors/theme-colors
(colors/resolve-color customization-color theme 20)
(colors/resolve-color customization-color theme 40)
theme)
bottom (reanimated/interpolate
distance-from-list-top
[0 messages.constants/header-container-top-margin]
[32 -4]
messages.constants/default-extrapolation-option)
background-opacity (reanimated/interpolate
distance-from-list-top
[messages.constants/header-container-top-margin
(+ messages.constants/header-animation-distance
messages.constants/header-container-top-margin)]
[1 0]
messages.constants/default-extrapolation-option)]
group-chat color description
last-message]} chat
display-name (cond
(= chat-type constants/one-to-one-chat-type)
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
(= chat-type constants/community-chat-type)
(str "# " chat-name)
:else (str emoji chat-name))
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
online? (rf/sub [:visibility-status-updates/online? chat-id])
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))
photo-path (rf/sub [:chats/photo-path chat-id])
top-margin (+ (safe-area/get-top)
messages.constants/top-bar-height
messages.constants/header-container-top-margin
32)
background-color (colors/theme-colors
(colors/resolve-color customization-color theme 20)
(colors/resolve-color customization-color theme 40)
theme)
bottom (reanimated/interpolate
distance-from-list-top
[0 messages.constants/header-container-top-margin]
[32 -4]
messages.constants/default-extrapolation-option)
background-opacity (reanimated/interpolate
distance-from-list-top
[messages.constants/header-container-top-margin
(+ messages.constants/header-animation-distance
messages.constants/header-container-top-margin)]
[1 0]
messages.constants/default-extrapolation-option)]
[:<>
[reanimated/view
{:style (style/background-container background-color background-opacity top-margin)}]
@@ -254,18 +266,25 @@
:theme theme
:profile-picture photo-path
:group-chat group-chat
:color color}]
:color color
:emoji emoji
:chat-type chat-type
:chat-name chat-name
:last-message last-message}]
[chat-display-name
{:distance-from-list-top distance-from-list-top
:display-name display-name
:theme theme
:contact contact
:group-chat group-chat}]
:group-chat group-chat
:last-message last-message}]
[bio-and-actions
{:distance-from-list-top distance-from-list-top
:bio bio
:chat-id chat-id
:customization-color customization-color}]]]))
:customization-color customization-color
:description description
:last-message last-message}]]]))
(defn list-footer
[props]
@@ -1,5 +1,6 @@
(ns status-im.contexts.chat.messenger.messages.navigation.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[re-frame.db]
@@ -28,7 +29,7 @@
[:contacts/contact-two-names-by-identity
chat-id]))
(= chat-type constants/community-chat-type)
(str (when emoji (str emoji " ")) "# " chat-name)
(str "# " chat-name)
:else (str emoji chat-name))
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
photo-path (when-not group-chat (rf/sub [:chats/photo-path chat-id]))
@@ -53,7 +54,10 @@
{:customization-color color
:size :size-32
:picture photo-path
:override-theme :dark}]
:override-theme :dark
:emoji (when-not (string/blank? emoji)
(string/trim emoji))
:chat-name chat-name}]
[quo/user-avatar
{:full-name display-name
:online? online?
@@ -111,27 +115,29 @@
(defn view
[{:keys [distance-from-list-top chat-screen-layout-calculations-complete?]}]
(let [{:keys [chat-id chat-type] :as chat} (rf/sub [:chats/current-chat-chat-view])
all-loaded? (reanimated/use-shared-value false)
all-loaded-sub (rf/sub [:chats/all-loaded? chat-id])
top-insets (safe-area/get-top)
top-bar-height messages.constants/top-bar-height
navigation-view-height (+ top-bar-height top-insets)
navigation-buttons-opacity (worklets/navigation-buttons-complete-opacity
chat-screen-layout-calculations-complete?)
reached-threshold? (messages.worklets/use-messages-scrolled-to-threshold
distance-from-list-top
top-bar-height)
button-background (if reached-threshold? :photo :blur)]
(let [{:keys [chat-id chat-type last-message]
:as chat} (rf/sub [:chats/current-chat-chat-view])
all-loaded? (reanimated/use-shared-value false)
all-loaded-sub (rf/sub [:chats/all-loaded? chat-id])
top-insets (safe-area/get-top)
top-bar-height messages.constants/top-bar-height
navigation-view-height (+ top-bar-height top-insets)
navigation-buttons-opacity (worklets/navigation-buttons-complete-opacity
chat-screen-layout-calculations-complete?)
reached-threshold? (messages.worklets/use-messages-scrolled-to-threshold
distance-from-list-top
top-bar-height)
button-background (if reached-threshold? :photo :blur)]
(rn/use-effect (fn [] (reanimated/set-shared-value all-loaded? all-loaded-sub))
[all-loaded-sub])
[rn/view
{:style (style/navigation-view navigation-view-height messages.constants/pinned-banner-height)}
[animated-background-and-pinned-banner
{:chat-id chat-id
:navigation-view-height navigation-view-height
:distance-from-list-top distance-from-list-top
:all-loaded? all-loaded?}]
(when (seq last-message)
[animated-background-and-pinned-banner
{:chat-id chat-id
:navigation-view-height navigation-view-height
:distance-from-list-top distance-from-list-top
:all-loaded? all-loaded?}])
[rn/view {:style (style/header-container top-insets top-bar-height)}
[reanimated/view {:style (style/button-animation-container navigation-buttons-opacity)}
[quo/button
@@ -130,6 +130,9 @@
[cofx chat-id]
(navigation/show-bottom-sheet
cofx
{:content (fn [] [pinned-messages-menu/view
{:chat-id chat-id
:disable-message-long-press? (not= :chat (get-in cofx [:db :view-id]))}])}))
{:padding-bottom-override 21
:content (fn [] [pinned-messages-menu/view
{:chat-id chat-id
:disable-message-long-press? (not= :chat
(get-in cofx
[:db :view-id]))}])}))
@@ -31,5 +31,5 @@
(def divider
{:padding-horizontal 20
:margin-top 16
:margin-top 12
:margin-bottom 8})
@@ -53,7 +53,8 @@
[{:keys [title]}]
(when-not (= title no-title)
[quo/divider-label
{:container-style style/divider}
{:container-style style/divider
:tight? false}
title]))
(defn key-fn
@@ -7,7 +7,7 @@
[bottom-inset]
{:left 0
:right 0
:height (+ bottom-inset (if platform/ios? 65 85))
:height (+ bottom-inset (if platform/ios? 51 85))
:position :absolute
:bottom 0})
@@ -73,19 +73,25 @@
(defn check-permissions-to-join-for-selection
[{:keys [db]} [community-id addresses]]
(if (empty? addresses)
;; When there are no addresses we can't just check permissions, otherwise
;; status-go will consider all possible addresses and the user will see the
;; incorrect highest permission role.
{:db (update db :communities/permissions-checks-for-selection dissoc community-id)}
{:db (assoc-in db [:communities/permissions-checks-for-selection community-id :checking?] true)
:fx [[:json-rpc/call
[{:method :wakuext_checkPermissionsToJoinCommunity
:params [{:communityId community-id :addresses addresses}]
:on-success [:communities/check-permissions-to-join-during-selection-success
community-id]
:on-error [:communities/check-permissions-to-join-during-selection-failure
community-id addresses]}]]]}))
(let [request-id (rand-int 10000000)]
(if (empty? addresses)
;; When there are no addresses we can't just check permissions, otherwise
;; status-go will consider all possible addresses and the user will see the
;; incorrect highest permission role.
{:db (update db :communities/permissions-checks-for-selection dissoc community-id)}
{:db (update-in db
[:communities/permissions-checks-for-selection community-id]
assoc
:checking? true
:latest-request-id request-id)
:fx [[:json-rpc/call
[{:method :wakuext_checkPermissionsToJoinCommunity
:params [{:communityId community-id :addresses addresses}]
:id request-id
:on-success [:communities/check-permissions-to-join-during-selection-success
community-id]
:on-error [:communities/check-permissions-to-join-during-selection-failure
community-id addresses]}]]]})))
;; This event should be used to check permissions temporarily because it won't
;; mutate the state `:communities/permissions-check` (used by many other
@@ -94,12 +100,14 @@
check-permissions-to-join-for-selection)
(rf/reg-event-fx :communities/check-permissions-to-join-during-selection-success
(fn [{:keys [db]} [community-id result]]
{:db (assoc-in db
[:communities/permissions-checks-for-selection community-id]
{:checking? false
:based-on-client-selection? true
:check result})}))
(fn [{:keys [db]} [community-id result request-id]]
(when (= (get-in db [:communities/permissions-checks-for-selection community-id :latest-request-id])
request-id)
{:db (assoc-in db
[:communities/permissions-checks-for-selection community-id]
{:checking? false
:based-on-client-selection? true
:check result})})))
(rf/reg-event-fx :communities/check-permissions-to-join-during-selection-failure
(fn [_ [community-id addresses error]]
@@ -112,12 +112,14 @@
(defn actions
[{:keys [locked? chat-id community-id]} inside-chat? hide-view-members?]
(let [{:keys [muted muted-till chat-type]} (rf/sub [:chats/chat-by-id chat-id])]
(let [{:keys [muted muted-till chat-type]} (rf/sub [:chats/chat-by-id chat-id])
{:keys [token-gated?]} (rf/sub [:chats/community-chat-by-id community-id chat-id])]
(cond
locked?
[quo/action-drawer
[[(action-invite-people)
(action-token-requirements)
(when token-gated?
(action-token-requirements))
(action-qr-code chat-id)
(action-share chat-id)]]]
@@ -135,7 +137,8 @@
(and inside-chat? (not locked?))
[quo/action-drawer
[[(action-view-members-and-details community-id chat-id)
(action-token-requirements)
(when token-gated?
(action-token-requirements))
(action-mark-as-read)
(action-toggle-muted chat-id muted muted-till chat-type)
(action-notification-settings)
@@ -23,7 +23,7 @@
(h/test "joined options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:token-permissions []}})
:role-permissions? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -52,7 +52,7 @@
(h/test "admin options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:admin true
:token-permissions []}})
:role-permissions? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -76,7 +76,7 @@
(h/test "request sent options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join "mock-id"
:communities/community {:token-permissions []}})
:communities/community {:role-permissions? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/invite-people-from-contacts))
(h/is-truthy (h/get-by-translation-text :t/view-token-gating))
@@ -97,7 +97,7 @@
(h/test "banned options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:token-permissions []}})
:role-permissions? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/invite-people-from-contacts))
(h/is-truthy (h/get-by-translation-text :t/view-token-gating))
@@ -107,7 +107,7 @@
(h/test "banned options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:token-permissions []}})
:role-permissions? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/invite-people-from-contacts))
(h/is-truthy (h/get-by-translation-text :t/view-token-gating))
@@ -118,6 +118,6 @@
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:muted true
:token-permissions []}})
:role-permissions? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/unmute-community))))
@@ -180,16 +180,16 @@
(defn get-context-drawers
[{:keys [id]}]
(let [{:keys [token-permissions admin joined
(let [{:keys [role-permissions? admin joined
muted banList muted-till color
intro-message]} (rf/sub [:communities/community id])
request-id (rf/sub [:communities/my-pending-request-to-join id])]
(cond
admin (owner-options id token-permissions muted muted-till intro-message)
joined (joined-options id token-permissions muted muted-till color intro-message)
request-id (join-request-sent-options id token-permissions request-id intro-message)
banList (banned-options id token-permissions intro-message)
:else (not-joined-options id token-permissions request-id intro-message))))
admin (owner-options id role-permissions? muted muted-till intro-message)
joined (joined-options id role-permissions? muted muted-till color intro-message)
request-id (join-request-sent-options id role-permissions? request-id intro-message)
banList (banned-options id role-permissions? intro-message)
:else (not-joined-options id role-permissions? request-id intro-message))))
(defn community-options-bottom-sheet
[id]
@@ -0,0 +1,48 @@
(ns status-im.contexts.communities.actions.invite-contacts.style
(:require
[quo.foundations.colors :as colors]
[react-native.safe-area :as safe-area]))
(def contact-selection-heading
{:flex-direction :row
:justify-content :space-between
:align-items :flex-end
:margin-top 24
:margin-bottom 16})
(def chat-button
{:position :absolute
:bottom (safe-area/get-bottom)
:left 20
:right 20})
(defn no-contacts
[]
{:margin-bottom (+ 96 (safe-area/get-bottom))
:flex 1
:justify-content :center
:align-items :center})
(def context-tag
{:align-self :flex-start
:margin-top -8
:margin-bottom 12})
(def no-contacts-text
{:margin-bottom 2
:margin-top 12})
(def no-contacts-button-container
{:margin-top 20
:margin-bottom 12})
(defn section-list-container-style
[theme]
{:padding-bottom 70
:background-color (colors/theme-colors colors/white
colors/neutral-95
theme)})
(defn invite-to-community-text
[theme]
{:color (colors/theme-colors colors/neutral-100 colors/white theme)})
@@ -0,0 +1,141 @@
(ns status-im.contexts.communities.actions.invite-contacts.view
(:require
[quo.core :as quo]
[quo.foundations.resources :as resources]
[quo.theme]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.share :as share]
[status-im.common.contact-list-item.view :as contact-list-item]
[status-im.common.contact-list.view :as contact-list]
[status-im.contexts.communities.actions.invite-contacts.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- no-contacts-view
[{:keys [theme id]}]
(let [customization-color (rf/sub [:profile/customization-color])
{:keys [universal-profile-url]} (rf/sub [:profile/profile])
on-press-share-community (rn/use-callback
#(rf/dispatch [:communities/share-community-url-with-data
id]))
on-press-share-profile (rn/use-callback #(share/open {:url universal-profile-url})
[universal-profile-url])]
[rn/view
{:style (style/no-contacts)}
[rn/image {:source (resources/get-themed-image :no-contacts-to-chat theme)}]
[quo/text
{:weight :semi-bold
:size :paragraph-1
:style style/no-contacts-text}
(i18n/label :t/you-have-no-contacts)]
[quo/text
{:weight :regular
:size :paragraph-2}
(i18n/label :t/dont-yell-at-me)]
[quo/button
{:customization-color customization-color
:theme theme
:type :primary
:size 32
:container-style style/no-contacts-button-container
:on-press on-press-share-community}
(i18n/label :t/send-community-link)]
[quo/button
{:customization-color customization-color
:theme theme
:type :grey
:size 32
:on-press on-press-share-profile}
(i18n/label :t/invite-friends-to-status)]]))
(defn- contact-item
[{:keys [public-key]
:as item}]
(let [user-selected? (rf/sub [:is-contact-selected? public-key])
{:keys [id]} (rf/sub [:get-screen-params])
community-members-keys (set (keys (rf/sub [:communities/community-members id])))
community-member? (boolean (community-members-keys public-key))
on-toggle (fn []
(when-not community-member?
(if user-selected?
(rf/dispatch [:deselect-contact public-key])
(rf/dispatch [:select-contact public-key]))))]
[contact-list-item/contact-list-item
{:on-press on-toggle
:allow-multiple-presses? true
:accessory {:type :checkbox
:disabled? community-member?
:checked? (or community-member? user-selected?)
:on-check on-toggle}
:disabled? community-member?}
item]))
(defn view-internal
[{:keys [theme]}]
(fn []
(rn/use-unmount #(rf/dispatch [:group-chat/clear-contacts]))
(let [customization-color (rf/sub [:profile/customization-color])
{:keys [id]} (rf/sub [:get-screen-params])
contacts (rf/sub [:contacts/filtered-active-sections])
selected (rf/sub [:group/selected-contacts])
{:keys [name images]} (rf/sub [:communities/community id])
selected-contacts-count (count selected)
on-press (fn []
(rf/dispatch [:communities/share-community-confirmation-pressed
selected id])
(rf/dispatch [:navigate-back])
(rf/dispatch [:toasts/upsert
{:type :positive
:theme theme
:text (if (= 1 selected-contacts-count)
(i18n/label :t/one-user-was-invited)
(i18n/label
:t/n-users-were-invited
{:count selected-contacts-count}))}]))
{window-height :height} (rn/get-window)]
[rn/view {:style {:flex 1}}
[rn/view {:style {:padding-horizontal 20}}
[quo/button
{:type :grey
:size 32
:icon-only? true
:on-press #(rf/dispatch [:navigate-back])}
:i/close]
[rn/view {:style style/contact-selection-heading}
[quo/text
{:weight :semi-bold
:size :heading-1
:style (style/invite-to-community-text theme)}
(i18n/label :t/invite-to-community)]]
[quo/context-tag
{:type :community
:size 24
:community-logo (:thumbnail images)
:community-name name
:container-style style/context-tag}]]
(if (empty? contacts)
[no-contacts-view
{:theme theme
:id id}]
[:<>
[gesture/section-list
{:key-fn :public-key
:sticky-section-headers-enabled true
:sections contacts
:render-section-header-fn contact-list/contacts-section-header
:content-container-style (style/section-list-container-style theme)
:render-fn contact-item
:style {:height window-height}}]
(when (pos? selected-contacts-count)
[quo/button
{:type :primary
:accessibility-label :next-button
:customization-color customization-color
:container-style style/chat-button
:on-press on-press}
(if (= 1 selected-contacts-count)
(i18n/label :t/invite-1-user)
(i18n/label :t/invite-n-users {:count selected-contacts-count}))])])])))
(def view (quo.theme/with-theme view-internal))
@@ -1,5 +0,0 @@
(ns status-im.contexts.communities.actions.permissions-sheet.style)
(def container
{:flex 1
:padding-horizontal 20})
@@ -1,38 +1,12 @@
(ns status-im.contexts.communities.actions.permissions-sheet.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[status-im.contexts.communities.actions.permissions-sheet.style :as style]
[status-im.contexts.communities.utils :as communities.utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- role-text
[role]
(i18n/label
(communities.utils/role->translation-key role
:t/token-master)))
(defn- permission-view
[{:keys [tokens role satisfied?]}]
(when (seq tokens)
^{:key role}
[rn/view {:style {:margin-bottom 20}}
[quo/text {:weight :medium}
(if satisfied?
(i18n/label :t/you-eligible-to-join-as {:role (role-text role)})
(i18n/label :t/you-not-eligible-to-join-as {:role (role-text role)}))]
[quo/text {:size :paragraph-2 :style {:padding-bottom 8}}
(if satisfied?
(i18n/label :t/you-hodl)
(i18n/label :t/you-must-hold))]
[quo/token-requirement-list
{:tokens tokens
:container-style {:padding-horizontal 20}}]]))
(defn view
[id]
(let [permissions (rf/sub [:community/token-permissions id])]
[gesture/scroll-view {:style style/container}
(map permission-view permissions)]))
[gesture/scroll-view
[quo/community-detail-token-gating {:permissions permissions}]
]))
+106 -62
View File
@@ -1,11 +1,8 @@
(ns status-im.contexts.communities.events
(:require
[clojure.string :as string]
[legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.data-store.communities :as data-store.communities]
[legacy.status-im.mailserver.core :as mailserver]
[react-native.platform :as platform]
[react-native.share :as share]
[schema.core :as schema]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
@@ -14,9 +11,9 @@
status-im.contexts.communities.actions.airdrop-addresses.events
status-im.contexts.communities.actions.community-options.events
status-im.contexts.communities.actions.leave.events
[status-im.contexts.communities.utils :as utils]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn handle-community
@@ -152,43 +149,83 @@
:on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %])
:on-error #(log/error "failed to fetch collapsed community categories" %)}]}))
(rf/reg-event-fx :communities/get-community-channel-share-data
(fn [_ [chat-id on-success]]
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
{:json-rpc/call
[{:method "wakuext_shareCommunityChannelURLWithData"
:params [{:CommunityID community-id :ChannelID channel-id}]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community channel url with data"
{:error err
:chat-id chat-id
:event :communities/get-community-channel-share-data}))}]})))
(defn update-previous-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
(let [accounts (utils/sorted-non-watch-only-accounts db)
selected-permission-addresses (get-in db
[:communities community-id
:selected-permission-addresses])
selected-accounts (filter #(contains? selected-permission-addresses (:address %))
accounts)
current-airdrop-address (get-in db [:communities community-id :airdrop-address])
share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])]
{:db (update-in db
[:communities community-id]
assoc
:previous-share-all-addresses? share-all-addresses?
:previous-permission-addresses selected-permission-addresses
:airdrop-address (if (contains? selected-permission-addresses
current-airdrop-address)
current-airdrop-address
(:address (first selected-accounts))))})))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content title}
:item {:default {:type "url"
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/update-previous-permission-addresses
update-previous-permission-addresses)
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
(fn [_ [chat-id]]
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
{:chat-id chat-id
:url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(defn toggle-selected-permission-address
[{:keys [db]} [address community-id]]
(let [selected-permission-addresses
(get-in db [:communities community-id :selected-permission-addresses])
updated-selected-permission-addresses
(if (contains? selected-permission-addresses address)
(disj selected-permission-addresses address)
(conj selected-permission-addresses address))]
{:db (assoc-in db
[:communities community-id :selected-permission-addresses]
updated-selected-permission-addresses)
:fx [(when community-id
[:dispatch
[:communities/check-permissions-to-join-community community-id
updated-selected-permission-addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-selected-permission-address
toggle-selected-permission-address)
(defn toggle-share-all-addresses
[{:keys [db]} [community-id]]
(let [share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])
next-share-all-addresses? (not share-all-addresses?)
accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (update-in db
[:communities community-id]
assoc
:share-all-addresses? next-share-all-addresses?
:selected-permission-addresses addresses)
:fx [(when (and community-id next-share-all-addresses?)
[:dispatch
[:communities/check-permissions-to-join-community community-id
addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-share-all-addresses
toggle-share-all-addresses)
(rf/reg-event-fx :communities/reset-selected-permission-addresses
(fn [{:keys [db]} [community-id]]
(when community-id
{:db (update-in db
[:communities community-id]
assoc
:selected-permission-addresses
(get-in db [:communities community-id :previous-permission-addresses])
:share-all-addresses?
(get-in db [:communities community-id :previous-share-all-addresses?]))
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(rf/reg-event-fx :communities/set-airdrop-address
(fn [{:keys [db]} [address community-id]]
{:db (assoc-in db [:communities community-id :airdrop-address] address)}))
(defn community-fetched
[{:keys [db]} [community-id community]]
@@ -288,8 +325,8 @@
(rf/reg-event-fx :chat.ui/spectate-community spectate-community)
(rf/defn navigate-to-serialized-community
[_ {:keys [community-id]}]
(defn navigate-to-serialized-community
[community-id]
{:serialization/deserialize-and-compress-key
{:serialized-key community-id
:on-success #(rf/dispatch [:communities/navigate-to-community-overview %])
@@ -297,27 +334,30 @@
:error %
:community-id community-id})}})
(rf/reg-event-fx :communities/navigate-to-community-overview
(fn [{:keys [db] :as cofx} [deserialized-key]]
(let [current-view-id (:view-id db)]
(if (string/starts-with? deserialized-key constants/serialization-key)
(navigate-to-serialized-community cofx deserialized-key)
(rf/merge
cofx
{:fx [[:dispatch
[:communities/fetch-community
{:community-id deserialized-key
:update-last-opened-at? true}]]
[:dispatch [:navigate-to :community-overview deserialized-key]]
(when (get-in db [:communities deserialized-key :joined])
[:dispatch
[:activity-center.notifications/dismiss-community-overview deserialized-key]])]}
(when-not (or (= current-view-id :shell) (= current-view-id :communities-stack))
(navigation/pop-to-root :shell-stack)))))))
(rf/defn navigate-to-community-overview
[{:keys [db] :as cofx} [community-id]]
(let [current-view-id (:view-id db)]
(if (string/starts-with? community-id constants/serialization-key)
(navigate-to-serialized-community community-id)
(rf/merge
cofx
{:fx [[:dispatch
[:communities/fetch-community
{:community-id community-id
:update-last-opened-at? true}]]
[:dispatch [:navigate-to :community-overview community-id]]
(when (get-in db [:communities community-id :joined])
[:dispatch
[:activity-center.notifications/dismiss-community-overview community-id]])]}
(when-not (or (= current-view-id :shell) (= current-view-id :communities-stack))
(navigation/pop-to-root :shell-stack))))))
(rf/reg-event-fx :communities/navigate-to-community-chat
(fn [{:keys [db]} [chat-id pop-to-root?]]
(let [{:keys [community-id]} (get-in db [:chats chat-id])]
(rf/reg-event-fx :communities/navigate-to-community-overview navigate-to-community-overview)
(defn navigate-to-community-chat
[{:keys [db]} [chat-id pop-to-root? community-id]]
(let [community-id (or community-id (get-in db [:chats chat-id :community-id]))]
(merge
{:fx [(when community-id
[:dispatch
[:communities/fetch-community
@@ -325,7 +365,11 @@
:update-last-opened-at? true}]])
(if pop-to-root?
[:dispatch [:chat/pop-to-root-and-navigate-to-chat chat-id]]
[:dispatch [:chat/navigate-to-chat chat-id]])]})))
[:dispatch [:chat/navigate-to-chat chat-id]])]}
(when-not (get-in db [:chats chat-id :community-id])
{:db (assoc-in db [:chats chat-id :community-id] community-id)}))))
(rf/reg-event-fx :communities/navigate-to-community-chat navigate-to-community-chat)
(defn get-revealed-accounts
[{:keys [db]} [community-id on-success]]
@@ -11,7 +11,7 @@
(def community-tag-container
{:padding-horizontal screen-horizontal-padding
:margin-horizontal (- screen-horizontal-padding)
:margin-bottom 20})
:margin-bottom 16})
(def community-content-container
{:padding-horizontal screen-horizontal-padding})
@@ -64,4 +64,5 @@
{:border-radius 16
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80)
:border-width 1
:padding-top 10})
:padding-top 10
:margin-bottom 106})
@@ -38,8 +38,9 @@
{:keys [name emoji muted? id mentions-count unread-messages? on-press locked? color] :as chat}]
(let [sheet-content [actions/chat-actions
(assoc chat
:chat-type constants/community-chat-type
:chat-id (str community-id id))
:community-id community-id
:chat-type constants/community-chat-type
:chat-id (str community-id id))
false]
notification (cond
muted? :mute
@@ -161,7 +162,7 @@
(i18n/label :t/you-eligible-to-join-as {:role highest-role-text})
(i18n/label :t/you-not-eligible-to-join))]
[info-button]]
[quo/text {:style {:padding-horizontal 12 :padding-bottom 18} :size :paragraph-2}
[quo/text {:style {:padding-horizontal 12 :padding-bottom 6} :size :paragraph-2}
(if can-request-access?
(i18n/label :t/you-hodl)
(i18n/label :t/you-must-hold))]
@@ -175,7 +176,7 @@
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}]))
:accessibility-label :join-community-button
:customization-color color
:container-style {:margin-horizontal 12 :margin-top 12 :margin-bottom 12}
:container-style {:margin-horizontal 12 :margin-top 8 :margin-bottom 12}
:disabled? (not can-request-access?)
:icon-left (if can-request-access? :i/unlocked :i/locked)}
(i18n/label :t/request-to-join)]])))
@@ -0,0 +1,101 @@
(ns status-im.contexts.communities.sharing.events
(:require [legacy.status-im.data-store.chats :as data-store.chats]
[react-native.platform :as platform]
[react-native.share :as share]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :invite-people-community {:id id}]]]}))
(rf/reg-event-fx :communities/share-community-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {:id id}]]]}))
(rf/reg-event-fx :communities/share-community-confirmation-pressed
(fn [_ [users-public-keys community-id]]
{:fx [[:json-rpc/call
[{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users users-public-keys}]
:js-response true
:on-success [:sanitize-messages-and-process-response]
:on-error (fn [err]
(log/error {:message "failed to share community"
:community-id community-id
:err err}))}]]]}))
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
(fn [_ [chat-id]]
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
{:chat-id chat-id
:url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(rf/dispatch [:open-share
{:options (if platform/ios?
{:activityItemSources
[{:placeholderItem {:type :text
:content title}
:item {:default {:type :url
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})}]))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/get-community-channel-share-data
(fn [_ [chat-id on-success]]
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
{:json-rpc/call
[{:method "wakuext_shareCommunityChannelURLWithData"
:params [{:CommunityID community-id :ChannelID channel-id}]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community channel url with data"
{:error err
:chat-id chat-id
:event :communities/get-community-channel-share-data}))}]})))
(rf/reg-event-fx :communities/get-community-share-data
(fn [_ [community-id on-success]]
{:json-rpc/call
[{:method "wakuext_shareCommunityURLWithData"
:params [community-id]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community url with data"
{:error err
:community-id community-id
:event :communities/get-community-share-data}))}]}))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content title}
:item {:default {:type "url"
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))

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