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
120 changed files with 1863 additions and 1219 deletions
+2
View File
@@ -35,3 +35,5 @@ 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
+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'
}
} }
}
-3
View File
@@ -57,9 +57,6 @@
[Troubleshooting for known errors](troubleshooting.md)
## Articles
[Notes on memoization](./articles/notes-on-memoization.md)
## Outdated:
-155
View File
@@ -1,155 +0,0 @@
# Notes on Memoization
## Content
- [Intro](#intro)
- [Understanding Object.is for Effective Memoization](#understanding-objectis-for-effective-memoization)
- [Strategies for Creating Stable References](#strategies-for-creating-stable-references)
- [Key Takeaways](#key-takeaways)
- [Further Readings](#further-readings)
## Intro
Memoization is an optimization technique used to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. It is a specific form of caching where results of function calls are stored based on their input arguments. However, it's important to note that this technique introduces memory overhead, as it requires additional space to store the results of function calls. While it is a powerful tool for improving performance, we must balance its benefits against the increased memory usage, especially in resource-constrained environments.
Memoization in React is used to ensure that a component or a computation does not re-render or re-calculate `unnecessarily` when its input props or dependencies have not changed. This technique can significantly improve the performance of React applications, especially for `expensive`, `computation-heavy` operations or components that `render frequently`.
In React, we generally have three util functions for memoizing :
- React.memo (or `rn/memo` in the case of our codebase):
Memoizes a component's render output based on its props. It can optionally take a comparison function to customize how changes in props are detected.
e.g:
```clojure
(def pure-component
(rn/memo (fn [props]
[view props])
(fn [prev-props next-props]
(= (:value prev-props) (:value next-props)))))
```
- React.useMemo (or `rn/use-memo` in the case of our codebase): Memoizes a computed value so that it does not need to be re-calculated on every render, given that its dependencies haven't changed. e.g:
```clojure
(defn component [{:keys [something] :as props}]
(let [memoized-value (rn/use-memo (fn []
(compute-expensive-value something))
[something])]
[view {:value memoized-value}]))
```
- React.useCallback (or `rn/use-callback` in the case of our codebase): Similar to React.useMemo, but for callback functions, ensuring that a function's reference remains stable between renders unless its dependencies change. e.g:
```clojure
(defn component [{:keys [something] :as props}]
(let [on-done (rn/use-callback (fn []
(do-some-thing something))
[something])]
[view {:on-done on-done}]))
```
Based on the example for the 3 util functions above, we generally see that they take a second argument which is a function in the case of `React.memo` and is a dependency array (vector) for `React.useMemo` and `React.useCallback` respectively.
In the case of `React.memo`, the second argument been a function is essentially a predicate which we as the consumer use to determine (or tell React) if the previous prop used to render a component is equal to the next prop it would receive. If this results to `true` we get to skip re-rendering, otherwise we re-render with the new props. The argument is also optional, and if not provided React would use Javascript's [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) to compare each individual prop (previous prop against next prop) for equality.
## Understanding Object.is for Effective Memoization
For `React.useMemo` and `React.useCallback` the second argument is a required array (vector) of dependencies which we as the consumer do not have any control of telling React how to compare the value of the previous prop to the next prop. It also uses [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) for comparisons of each of the dependencies.
Since, we do not have a way of telling React of how to compare the previous prop to the next prop in the case of `React.useMemo` and `React.useCallback`, it would benefit us to understand how `Object.is` compares values.
There are two types of values in Javascript (Javascript is used here because our Clojurescript code essentially compiles to Javascript when we are using pure React):
- Primitive Values: e.g string, number, boolean, BigInt, null, undefined and Symbol
- Reference (non primitive) values: These are all other values asides the ones stated above. e.g objects (map), arrays (vectors), sets, functions etc...
For primitive values, `Object.is` can simply compare them by their actual values e.g `Object.is(2, 2)` or `Object.is("John Snow", "John Snow")` would always return `true` because the values are essentially the same.
For reference values, `Object.is` compares them based on the reference (which for simplicity are like pointers to values stored in a variable). For example:
```js
const a = {}
const b = {}
Object.is(a, b)
```
`Object.is(a, b)` would return `false` even though the values are actually equal. This is because the reference to `a` and `b` are essentially different and they been non-primitive (reference values), `Object.is` compares them with their references rather than their values. The same holds true if we inline the variables like so `Object.is({}, {})`.
Now that we understand this, let us see how this relates to React. In React functional component we essentially create a function which can have some variables bound to its scope and return some UI. e.g:
```clojure
(defn component [props]
(let [some-state (rn/use-state {:name "John Snow"
:knowledge 0})
some-vector [1 2 3]]
[rn/view props]))
```
When the `props` or the bound `"reactive" state` of this component changes, React would re-render this component by executing the function body and by doing so all variables bound to this function's scope are re-initialized meaning they get a new reference if they are reference (non primitive) values. This "re-initialization" and changing of reference happens anytime there is a re-render even if the actual value(s) did not change between renders. This means that any non primitive value in the scope of the function component would by default have an `unstable reference` between every render cycle.
By having an unstable reference, `Object.is` would always return `false` for reference values when comparing them between cycles. The implication of this is that when you pass a reference value with an unstable reference as a prop to a component that is memoized via `React.memo` (without a custom compare function), React would always see that prop as new value when comparing hence re-rendering the component because the reference to the prop changed even when the value hasn't essentially changed. The same is also true for `React.useMemo` and `React.useCallback`, if an unstable reference is passed as a dependency to them, the function they would recompute their function body and return a new reference to the memoized `value` or `callback`. By passing an unstable value (reference value) to these util functions, we essentially defeat the purpose of memoization in the first place.
## Strategies for Creating Stable References
How then do we create stable references? Well there are a number of ways, and there is no one size fits all way of doing it. But here are a few ways we could do it:
### Using global variables:
We can get stable references by declaring variables in a more global scope relative to the function's local scope. This essentially means if we can, we should declare the variables outside the functions scope. e.g:
```clojure
(def some-map {:name "John Snow"
:knowledge 0})
(defn comp []
[rn/view {:some-map some-map}])
```
- Trade-offs: While global variables ensure reference stability, they can introduce side effects or global state management complexities. Overuse can make components harder to understand and test due to implicit dependencies.
- Best Scenarios: Use for constants or configuration data that truly does not change over the application's lifetime and does not belong to any component's state.
### Using `React.useRef`:
Provides a mutable ref object that remains constant throughout the component's lifecycle. Ideal for holding onto a value that does not trigger re-renders.. e.g
```clojure
(defn comp []
(let [ref (rn/use-ref-atom {:name "John Snow"
:knowledge 0})]
[rn/view {:some-map @ref}]))
```
- Trade-offs: It is not "reactive", meaning changes to its content do not cause the component to re-render. It's best used for values that are incidental to rendering.
- Best Scenarios: Storing references to DOM elements, keeping track of previous props or state for comparison, or holding values that interact with imperative APIs.
### Using `React.useState`:
Returns a stable reference to a stateful value, with an updater function to change its value. The reference only changes when explicitly updated via the updater. e.g
```clojure
(defn comp []
(let [[state set-state] (rn/use-state {:name "John Snow"
:knowledge 0})]
[rn/view {:some-map state}]))
```
- Trade-offs: It triggers a component re-render when the state changes, which might not be necessary for all types of stored values. Managing large sets of stateful data here can make the component less efficient.
- Best Scenarios: Managing local component state that directly influences the render output. Ideal for values that change over time and need to trigger updates.
### Using `React.useMemo` and `React.useCallback`:
They both return stable references, but the catch here is that they also require stable references themselves as dependencies. e.g
```clojure
(defn comp []
(let [[knowledge set-knowledge] (rn/use-state 0)
derived-state (rn/use-memo (fn []
{:name "John Snow"
:knowledge knowledge})
[knowledge])]
[rn/view {:some-map derived-state}]))
```
- Trade-offs: They depend on the stability of their dependency lists, which can lead to unnecessary recalculations if dependencies have unstable references. Overuse can lead to increased memory usage and complexity.
- Best Scenarios: `React.useMemo` is best for expensive calculations that depend on specific props or state and do not change on every render. `React.useCallback` is ideal when passing callback functions to deeply nested child components that need stable references to prevent unnecessary renders.
The list is not exhaustive, but these are the most common ways to get stable references to non-primitive values.
It might be worthy to note that most of these would be abstracted away in React 19 with the introduction of a [compiler that helps you memo when needed](https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024#react-compiler).
## Key Takeaways
- Before you memo, profile to identify performance bottlenecks
- When you do want to memo, ensure you pass non primitive values with stable references as a dependency to your memo function.
- You do not need to worry about primitive values that much as they are easily comparable
- Memoization introduces its own complexity and memory overhead. Use it judiciously.
## Further Readings
- [Thinking in React](https://react.dev/learn/thinking-in-react)
- [Mastering Reacts Stable Values](https://shopify.engineering/master-reacts-stable-values)
+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.
+22 -41
View File
@@ -1,13 +1,5 @@
# UI components coding guidelines
## Content
- [Global State and Subscriptions](#global-state-and-subscriptions)
- [Regular Atoms](#regular-atoms)
- [Effects](#effects)
- [Performance Tips](#performance-tips)
- [Component Creation](#component-creation)
- [Component Updates](#component-updates)
> [!IMPORTANT]
> React apps are made out of components. A component is a piece of the UI (user interface) that has its own logic and appearance. A component can be as small as a button, or as large as an entire screen.
> React components are JavaScript functions that return markup
@@ -57,46 +49,36 @@ NOW:
- State values no longer need to be dereferenced; they are accessible as regular symbols. This eliminates a common bug where the "@" symbol was inadvertently omitted.
- `theme/with-theme` wrapper is not needed anymore, `(theme/use-theme-value)` hook can be used directly in the components
- `:f>` not needed anymore, all components are functional by default
- `rn/use-callback` hook should be used for anon callback functions
> [!IMPORTANT]
> For components that re-render frequently, ensure you pass stable reference as props, this might have you wrapping your callback in `rn/use-callback` or your maps/vectors in `rn/use-memo`
An example of such case would be when we have a heavy chat component which we only want to re-render when the props truly change, we can memoize callbacks/anon function passed to it.
> DO NOT USE anon functions directly in the props
BAD
```clojure
(defn view
[chat-id]
(let [[opened? set-opened] (rn/use-state false)]
[heavy-chat-component
{:style (style/main opened?)
:chats (get-chats chat-id)
:on-chat-open #(set-opened true)
:on-chat-close #(set-opened nil)}]))
[]
(let [[pressed? set-pressed] (rn/use-state false)]
[rn/pressable
{:style (style/main pressed?)
:on-press-in #(set-pressed true)
:on-press-out #(set-pressed nil)}]))
```
GOOD:
```clojure
(defn view
[]
(let [[opened? set-opened] (rn/use-state false)
chats (rn/use-memo #(get-chats chat-id)) [chat-id]
on-chat-open (rn/use-callback #(set-pressed true))
on-chat-close (rn/use-callback #(set-pressed nil))]
[heavy-chat-component
{:style (style/main opened?)
:chats chats
:on-chat-open on-chat-open
:on-chat-close on-chat-close}]))
(let [[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed nil))]
[rn/pressable
{:style (style/main pressed?)
:on-press-in on-press-in
:on-press-out on-press-out}]))
```
Note that this should be done for only components that re-renders more frequently, as so you should profile before using. This is because you might not benefit from using it much in scenarios
Note that this should be done for only components that re-renders more frequently. Therefore, it's advisable to conduct profiling to identify these type of components beforehand. This is because the benefits of employing this technique may not be substantial in cases where the component re-renders sparingly.
When in doubt refer to the [notes on memoization](./articles/notes-on-memoization.md) to get some clarification.
## Global State and Subscriptions
## Global state and subscriptions
For global state management, we utilize Re-frame subscriptions. They can be likened to React state. To obtain the state, `(rf/sub [])` is employed, and to modify it, `(rf/dispatch [])` is utilized. However, they update components in a similar manner to React states.
@@ -115,7 +97,7 @@ For global state management, we utilize Re-frame subscriptions. They can be like
[activity/view]]))
```
### Regular Atoms
## Regular atoms
In certain instances, components utilized regular atoms; however, they should now be used with `rn/use-ref-atom`
@@ -140,7 +122,7 @@ NOW:
on-blur (rn/use-callback #(reset! focused? false))]))
```
### Effects
## Effects
LIFECYCLE:
@@ -188,13 +170,13 @@ Instead `:all-collectibles-changed` should be used in the handler which changes
## Performance Tips
## Performance tips
To begin with, we need to understand that there are two distinct stages for a component: creation and update. React creates a render tree, a UI tree, composed of the rendered components.
![react_tree.png](react_tree.png)
### Component Creation
### Component creation
For component creation, the most critical factor is the number of elements involved, so we should strive to minimize them. For instance, it's advisable to avoid using unnecessary wrappers or containers.
@@ -216,7 +198,7 @@ GOOD:
[quo/button {:container-style {:padding-top 20}}]))
```
### Component Updates
### Component updates
For component updates, it's crucial to recognize that React will invoke the function where state is utilized. Therefore, if you utilize state in the root component, React will execute the root function and re-render the entire root component along with all its children (unless optimizations like memoization are implemented).
@@ -270,4 +252,3 @@ GOOD:
```
So, now the screen component function will never be invoked, and `component` and `component2` will be re-rendered only when `label` or `label2` have changed.
+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
+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

@@ -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))]}]]))))
+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?)
@@ -10,5 +10,5 @@
[:blur? {:optional true} [:maybe :boolean]]
[:collectible-img-src :schema.common/image-source]
[:collectible-name :string]
[:collectible-id :string]]]]
[:collectible-id {:optional true} [:maybe :string]]]]]
:any])
@@ -41,12 +41,13 @@
: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 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)
@@ -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}]
+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
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)))))
+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
+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"
+13
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]))
@@ -70,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.")
+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)}})
@@ -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)})
@@ -30,7 +30,7 @@
(defn pinned-message
[{:keys [from quoted-message timestamp-str]}]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
one-to-one-chat? (rf/sub [:current-chat/one-to-one-chat?])
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
@@ -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?
@@ -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}])))
@@ -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]))}])}))
@@ -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]]
@@ -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}]
]))
+30 -40
View File
@@ -149,23 +149,6 @@
:on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %])
:on-error #(log/error "failed to fetch collapsed community categories" %)}]}))
(defn initialize-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
(let [accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (update-in db
[:communities community-id]
assoc
:previous-share-all-addresses? true
:share-all-addresses? true
:previous-permission-addresses addresses
:selected-permission-addresses addresses
:airdrop-address (:address (first accounts)))})))
(rf/reg-event-fx :communities/initialize-permission-addresses
initialize-permission-addresses)
(defn update-previous-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
@@ -342,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 %])
@@ -351,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
@@ -379,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)]])))
@@ -38,23 +38,24 @@
:url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/share-community-url-with-data
(fn [_ [community-id]]
(let [title (i18n/label :t/community-on-status)
(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-share-data community-id on-success]]]})))
(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]]
@@ -49,12 +49,13 @@
[:screen/onboarding.enter-seed-phrase
:screen/onboarding.new-to-status]])}]
[rn/view {:style style/space-between-suboptions}]
[quo/small-option-card
{:variant :icon
:title (i18n/label :t/use-keycard)
:subtitle (i18n/label :t/use-keycard-subtitle)
:image (resources/get-image :use-keycard)
:on-press #(js/alert "TODO: to be implemented")}]]]))
(when config/show-not-implemented-features?
[quo/small-option-card
{:variant :icon
:title (i18n/label :t/use-keycard)
:subtitle (i18n/label :t/use-keycard-subtitle)
:image (resources/get-image :use-keycard)
:on-press #(js/alert "TODO: to be implemented")}])]]))
(defn getting-started-doc
[]
@@ -0,0 +1,68 @@
(ns status-im.contexts.preview.quo.community.community-detail-token-gating
(:require [quo.core :as quo]
[reagent.core :as reagent]
[status-im.common.resources :as resources]
[status-im.contexts.preview.quo.preview :as preview]))
(def descriptor
[{:key :role
:type :select
:options [{:key :one-role}
{:key :two-roles}
{:key :three-roles}
{:key :four-roles}]}])
(def roles
{:member
{:role 2
:role-text "Member"
:satisfied? true
:tokens
[[{:symbol "ETH" :sufficient? true :collectible? false :loading? false :amount 0.8 :img-src nil}]
[{:symbol "ETH" :sufficient? false :collectible? false :loading? false :amount 1 :img-src nil}
{:symbol "STT" :sufficient? false :collectible? false :loading? false :amount 10 :img-src nil}]]}
:admin
{:role 1
:role-text "Admin"
:satisfied? true
:tokens
[[{:symbol "ETH" :sufficient? true :collectible? false :loading? false :amount 2 :img-src nil}]]}
:token-master
{:role 5
:role-text "Token Master"
:satisfied? true
:tokens
[[{:symbol "TMANI"
:sufficient? true
:collectible? true
:loading? false
:amount nil
:img-src (resources/mock-images :collectible1)}]]}
:token-owner
{:role 6
:role-text "Token Owner"
:satisfied? true
:tokens
[[{:symbol "TOANI"
:sufficient? true
:collectible? true
:loading? false
:amount nil
:img-src (resources/mock-images :collectible)}]]}})
(def permissions
{:one-role [(:member roles)]
:two-roles [(:admin roles) (:member roles)]
:three-roles [(:token-master roles) (:admin roles) (:member roles)]
:four-roles [(:token-owner roles) (:token-master roles) (:admin roles) (:member roles)]})
(defn view
[]
(let [state (reagent/atom {:role :one-role})]
(fn []
[preview/preview-container
{:state state
:descriptor descriptor
:blur? (:blur? @state)
:show-blur-background? true}
[quo/community-detail-token-gating {:permissions ((:role @state) permissions)}]])))
@@ -39,6 +39,8 @@
channel-actions]
[status-im.contexts.preview.quo.community.community-card-view :as
community-card]
[status-im.contexts.preview.quo.community.community-detail-token-gating :as
community-detail-token-gating]
[status-im.contexts.preview.quo.community.community-membership-list-view
:as community-membership-list-view]
[status-im.contexts.preview.quo.community.community-stat :as community-stat]
@@ -260,6 +262,8 @@
:component color/view}]
:community [{:name :community-card-view
:component community-card/view}
{:name :community-detail-token-gating
:component community-detail-token-gating/view}
{:name :community-membership-list-view
:component community-membership-list-view/view}
{:name :community-stat
@@ -1,6 +1,7 @@
(ns status-im.contexts.preview.quo.wallet.network-link
(:require
[quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.preview.quo.preview :as preview]))
@@ -23,17 +24,21 @@
:options networks}
{:key :destination
:type :select
:options networks}])
:options networks}
{:key :width
:type :number}])
(defn view
[]
(let [state (reagent/atom {:shape :linear
:source :ethereum
:destination :optimism})]
:destination :optimism
:width 63})]
(fn []
[preview/preview-container
{:state state
:descriptor descriptor
:component-container-style {:padding-top 40
:align-items :center}}
[quo/network-link @state]])))
[rn/view {:style {:width (max (:width @state) 63)}}
[quo/network-link @state]]])))
@@ -48,7 +48,8 @@
(fn []
(rf/dispatch [:universal-links/generate-profile-url
{:public-key public-key
:on-success #(rn/sharing {:message %})}]))
:on-success #(rf/dispatch [:open-share
{:options {:message %}}])}]))
[public-key])
on-remove-contact (rn/use-callback
(fn []
@@ -1,6 +1,5 @@
(ns status-im.contexts.profile.contact.share.view
(:require [legacy.status-im.ui.components.list-selection :as list-selection]
[quo.core :as quo]
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.qr-codes.view :as qr-codes]
@@ -19,7 +18,8 @@
abbreviated-url (rn/use-memo (fn []
(address/get-abbreviated-profile-url universal-profile-url))
[universal-profile-url])
on-share-press (rn/use-callback #(list-selection/open-share {:message universal-profile-url})
on-share-press (rn/use-callback #(rf/dispatch [:open-share
{:options {:message universal-profile-url}}])
[universal-profile-url])
on-copy-press (rn/use-callback (fn []
(rf/dispatch [:share/copy-text-and-show-toast
@@ -34,7 +34,7 @@
(when config/show-not-implemented-features?
{:title (i18n/label :t/dapps)
:on-press not-implemented/alert
:image-props :i/placeholder
:image-props :i/dapps
:image :icon
:blur? true
:action :arrow})
@@ -5,7 +5,6 @@
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.common.not-implemented :as not-implemented]
[status-im.contexts.profile.settings.header.view :as settings.header]
[status-im.contexts.profile.settings.list-items :as settings.items]
[status-im.contexts.profile.settings.style :as style]
@@ -19,7 +18,7 @@
[rf/delay-render
[quo/category
{:list-type :settings
:container-style {:padding-bottom 0}
:container-style {:padding-bottom 12}
:blur? true
:data data}]])
@@ -62,7 +61,10 @@
:on-press #(rf/dispatch [:navigate-back])
:right-side [{:icon-name :i/qr-code
:on-press #(debounce/throttle-and-dispatch [:open-modal :share-shell] 1000)}
{:icon-name :i/share :on-press not-implemented/alert}]}]]
{:icon-name :i/share
:on-press #(rf/dispatch [:open-share
{:options {:message (:universal-profile-url
profile)}}])}]}]]
[rn/flat-list
{:key :list
:header [settings.header/view {:scroll-y scroll-y}]
@@ -22,13 +22,15 @@
(defn floating-button
[shared-values]
[quo/floating-shell-button
{:jump-to {:on-press #(animation/close-home-stack true)
:label (i18n/label :t/jump-to)
:customization-color (rf/sub [:profile/customization-color])}}
{:position :absolute
:bottom (utils/bottom-tabs-container-height)}
(:home-stack-opacity shared-values)])
(let [current-screen-id (rf/sub [:view-id])]
(when-not (= current-screen-id :settings)
[quo/floating-shell-button
{:jump-to {:on-press #(animation/close-home-stack true)
:label (i18n/label :t/jump-to)
:customization-color (rf/sub [:profile/customization-color])}}
{:position :absolute
:bottom (utils/bottom-tabs-container-height)}
(:home-stack-opacity shared-values)])))
(defn f-shell-stack
[]
@@ -71,13 +71,10 @@
(defn on-qr-code-scanned
[scanned-text]
(cond
(text-for-url-path? scanned-text router/community-with-data-path)
;; TODO: https://github.com/status-im/status-mobile/issues/18743
nil
(text-for-url-path? scanned-text router/channel-path)
;; TODO: https://github.com/status-im/status-mobile/issues/18743
nil
(or
(text-for-url-path? scanned-text router/community-with-data-path)
(text-for-url-path? scanned-text router/channel-path))
(debounce/debounce-and-dispatch [:universal-links/handle-url scanned-text] 300)
(text-for-url-path? scanned-text router/user-with-data-path)
(let [address (extract-id scanned-text)]
@@ -1,7 +1,6 @@
(ns status-im.contexts.shell.share.profile.view
(:require
[clojure.string :as string]
[legacy.status-im.ui.components.list-selection :as list-selection]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
@@ -30,7 +29,7 @@
:width (- window-width (* style/screen-padding 2))
:qr-data universal-profile-url
:qr-data-label-shown abbreviated-url
:on-share-press #(list-selection/open-share {:message universal-profile-url})
:on-share-press #(rf/dispatch [:open-share {:options {:message universal-profile-url}}])
:on-text-press #(rf/dispatch [:share/copy-text-and-show-toast
{:text-to-copy universal-profile-url
:post-copy-message (i18n/label :t/link-to-profile-copied)}])
@@ -4,7 +4,6 @@
[quo.core :as quo]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.share :as share]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.shell.share.style :as style]
@@ -20,18 +19,18 @@
(defn- share-action
[address share-title]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content address}
:item {:default {:type "text"
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})))
(rf/dispatch [:open-share
{:options (if platform/ios?
{:activityItemSources [{:placeholderItem {:type :text
:content address}
:item {:default {:type :text
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})}]))
(defn- open-preferences
[selected-networks account]
@@ -4,7 +4,6 @@
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[react-native.share :as share]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.wallet.account.share-address.style :as style]
@@ -18,18 +17,18 @@
(defn- share-action
[address share-title]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content address}
:item {:default {:type "text"
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})))
(rf/dispatch [:open-share
{:options (if platform/ios?
{:activityItemSources [{:placeholderItem {:type :text
:content address}
:item {:default {:type :text
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})}]))
(defn- open-preferences
[selected-networks]
@@ -6,20 +6,10 @@
[status-im.contexts.wallet.account.style :as style]
[status-im.contexts.wallet.account.tabs.view :as tabs]
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
[status-im.contexts.wallet.common.temp :as temp]
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn buy-drawer
[]
[:<>
[quo/drawer-top {:title (i18n/label :t/buy-tokens)}]
[rn/flat-list
{:data temp/buy-tokens-list
:style {:padding-horizontal 8
:padding-bottom 8}
:render-fn quo/settings-item}]])
(def first-tab-id :assets)
(defn tabs-data
@@ -55,7 +45,7 @@
:flow-id :wallet-flow}]))
:receive-action #(rf/dispatch [:open-modal :screen/wallet.share-address {:status :receive}])
:buy-action #(rf/dispatch [:show-bottom-sheet
{:content buy-drawer}])
{:content buy-token/view}])
:bridge-action #(rf/dispatch [:wallet/start-bridge])}])
[quo/tabs
{:style style/tabs
@@ -47,7 +47,7 @@
:image :icon
:image-props :i/derivated-path
:action :button
:action-props {:on-press #(if (ff/enabled? :ff/wallet.edit-derivation-path)
:action-props {:on-press #(if (ff/enabled? ::ff/wallet.edit-derivation-path)
(rf/dispatch [:standard-auth/authorize
{:on-auth-success on-auth-success
:auth-button-label (i18n/label :t/continue)}])
@@ -50,7 +50,8 @@
[account-switcher/view
{:on-press #(rf/dispatch [:navigate-back])
:icon-name :i/arrow-left
:accessibility-label :top-bar}]
:accessibility-label :top-bar
:switcher-type :select-account}]
[quo/page-top {:title bridge-to-title}]
[rn/view style/content-container
[bridge-token-component (assoc mainnet :network-name :t/mainnet) account-token]]
@@ -15,6 +15,7 @@
:button-one-label (i18n/label :t/confirm-bridge)
:button-one-props {:icon-left :i/bridge}
:on-navigate-back (fn []
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:navigate-back]))}]])
(def view (quo.theme/with-theme view-internal))
@@ -16,7 +16,8 @@
[rn/view {:style {:flex 1}}
[account-switcher/view
{:on-press #(rf/dispatch [:navigate-back])
:accessibility-label :top-bar}]
:accessibility-label :top-bar
:switcher-type :select-account}]
[quo/page-top {:title (i18n/label :t/bridge)}]
[quo/input
{:container-style style/input-container
@@ -1,6 +1,7 @@
(ns status-im.contexts.wallet.collectible.view
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]
@@ -36,10 +37,13 @@
:icon-left :i/send}
(i18n/label :t/send)]
[quo/button
{:container-style style/opensea-button
:type :outline
:size 40
:icon-left :i/opensea}
{:container-style style/opensea-button
:type :outline
:size 40
:icon-left :i/opensea
:icon-left-color (colors/theme-colors colors/neutral-100 colors/neutral-40)
:icon-right :i/external
:icon-right-color (colors/theme-colors colors/neutral-50 colors/neutral-40)}
(i18n/label :t/opensea)]])
(def tabs-data
@@ -1,52 +1,80 @@
(ns status-im.contexts.wallet.common.token-value.view
(:require [quo.core :as quo]
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
[status-im.feature-flags :as ff]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- action-buy
[]
{:icon :i/buy
:accessibility-label :buy
:label (i18n/label :t/buy)
:on-press #(rf/dispatch [:show-bottom-sheet
{:content buy-token/view}])
:right-icon :i/external})
(defn- action-send
[token-data]
{:icon :i/send
:accessibility-label :send
:label (i18n/label :t/send)
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:wallet/clean-send-data])
(rf/dispatch [:wallet/send-select-token
{:token token-data
:start-flow? true}]))})
(defn- action-receive
[]
{:icon :i/receive
:accessibility-label :receive
:label (i18n/label :t/receive)
:on-press #(rf/dispatch [:open-modal :screen/wallet.share-address {:status :receive}])})
(defn- action-bridge
[]
{:icon :i/bridge
:accessibility-label :bridge
:label (i18n/label :t/bridge)
:on-press #(js/alert "to be implemented")})
(defn- action-manage-tokens
[watch-only?]
{:icon :i/settings
:accessibility-label :settings
:label (i18n/label :t/manage-tokens)
:on-press #(js/alert "to be implemented")
:add-divider? (not watch-only?)})
(defn- action-hide
[]
{:icon :i/hide
:accessibility-label :hide
:label (i18n/label :t/hide)
:on-press #(js/alert "to be implemented")})
(defn token-value-drawer
[token]
[token watch-only?]
(let [token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered (:token token)]))]
[:<>
[quo/action-drawer
[[{:icon :i/buy
:accessibility-label :buy
:label (i18n/label :t/buy)
:on-press #(js/alert "to be implemented")
:right-icon :i/external}
{:icon :i/send
:accessibility-label :send
:label (i18n/label :t/send)
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:wallet/clean-send-data])
(rf/dispatch [:wallet/send-select-token
{:token token-data
:start-flow? true}]))}
{:icon :i/receive
:accessibility-label :receive
:label (i18n/label :t/receive)
:on-press #(js/alert "to be implemented")}
{:icon :i/bridge
:accessibility-label :bridge
:label (i18n/label :t/bridge)
:on-press #(js/alert "to be implemented")}
{:icon :i/settings
:accessibility-label :settings
:label (i18n/label :t/manage-tokens)
:on-press #(js/alert "to be implemented")
:add-divider? true}
{:icon :i/hide
:accessibility-label :hide
:label (i18n/label :t/hide)
:on-press #(js/alert "to be implemented")}]]]]))
[quo/action-drawer
[(cond->> [(when (ff/enabled? ::ff/wallet.assets-modal-manage-tokens)
(action-manage-tokens watch-only?))
(when (ff/enabled? ::ff/wallet.assets-modal-hide)
(action-hide))]
(not watch-only?) (concat [(action-buy)
(action-send token-data)
(action-receive)
(action-bridge)]))]]))
(defn view
[item _ _ {:keys [watch-only?]}]
[quo/token-value
(cond-> item
(not watch-only?)
(or (not watch-only?) (ff/enabled? ::ff/wallet.long-press-watch-only-asset))
(assoc :on-long-press
#(rf/dispatch
[:show-bottom-sheet
{:content (fn [] [token-value-drawer item])
{:content (fn [] [token-value-drawer item watch-only?])
:selected-item (fn [] [quo/token-value item])}])))])
@@ -0,0 +1,18 @@
(ns status-im.contexts.wallet.common.utils.networks
(:require [clojure.string :as string]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]))
(defn resolve-receiver-networks
[{:keys [prefix testnet-enabled? goerli-enabled?]}]
(let [prefix (if (string/blank? prefix)
constants/default-multichain-address-prefix
prefix)
prefix-seq (string/split prefix #":")]
(->> prefix-seq
(remove string/blank?)
(mapv
#(utils/network->chain-id
{:network %
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})))))
@@ -1,5 +1,6 @@
(ns status-im.contexts.wallet.common.utils.send
(:require [utils.money :as money]))
(:require [clojure.string :as string]
[utils.money :as money]))
(defn calculate-gas-fee
[data]
@@ -20,3 +21,17 @@
(defn calculate-full-route-gas-fee
[route]
(reduce money/add (map calculate-gas-fee route)))
(defn find-affordable-networks
[{:keys [balances-per-chain input-value selected-networks disabled-chain-ids]}]
(let [input-value (if (string/blank? input-value) 0 input-value)]
(->> balances-per-chain
(filter (fn [[_
{:keys [balance chain-id]
:or {balance 0}}]]
(and
(money/greater-than-or-equals (money/bignumber balance)
(money/bignumber input-value))
(some #(= % chain-id) selected-networks)
(not-any? #(= % chain-id) disabled-chain-ids))))
(map first))))
@@ -20,3 +20,44 @@
expected-eip1559-disabled-result (money/bignumber 0.000760406)]
(is (money/equal-to (utils/calculate-gas-fee data-eip1559-disabled)
expected-eip1559-disabled-result))))))
(deftest test-find-affordable-networks
(testing "All networks affordable and selected, none disabled"
(let [balances-per-chain {"1" {:balance "50.0" :chain-id "1"}
"2" {:balance "40.0" :chain-id "2"}}
input-value 20
selected-networks ["1" "2"]
disabled-chain-ids []
expected ["1" "2"]]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected)))))
(testing "No networks affordable"
(let [balances-per-chain {"1" {:balance "5.0" :chain-id "1"}
"2" {:balance "1.0" :chain-id "2"}}
input-value 10
selected-networks ["1" "2"]
disabled-chain-ids []
expected []]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected)))))
(testing "Selected networks subset, with some disabled"
(let [balances-per-chain {"1" {:balance "100.0" :chain-id "1"}
"2" {:balance "50.0" :chain-id "2"}
"3" {:balance "20.0" :chain-id "3"}}
input-value 15
selected-networks ["1" "2" "3"]
disabled-chain-ids ["2"]
expected ["1" "3"]]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected))))))
+1 -6
View File
@@ -2,12 +2,7 @@
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as rf]
[react-native.share :as share]))
(rf/reg-fx :effects.share/open
(fn [content]
(share/open content)))
[re-frame.core :as rf]))
(rf/reg-fx
:effects.wallet/create-account-from-mnemonic
+11 -11
View File
@@ -148,7 +148,7 @@
(let [tokens (data-store/rpc->tokens raw-tokens-data)
add-tokens (fn [stored-accounts tokens-per-account]
(reduce-kv (fn [accounts address tokens-data]
(if (accounts address)
(if (contains? accounts address)
(update accounts address assoc :tokens tokens-data)
accounts))
stored-accounts
@@ -402,16 +402,16 @@
(rf/reg-event-fx :wallet/share-account
(fn [_ [{:keys [content title]}]]
{:fx [[:effects.share/open
(if platform/ios?
{:activityItemSources
[{:placeholderItem {:type "text"
:content content}
:item {:default {:type "text"
:content content}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message content})]]}))
{:options (if platform/ios?
{:activityItemSources
[{:placeholderItem {:type :text
:content content}
:item {:default {:type :text
:content content}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message content})}]]}))
(rf/reg-event-fx
:wallet/blockchain-status-changed
@@ -23,5 +23,5 @@
(defn home-container
[]
{:margin-top (safe-area/get-top)
{:margin-top (+ (safe-area/get-top) 8)
:flex 1})
+77 -27
View File
@@ -5,6 +5,7 @@
[native-module.core :as native-module]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[status-im.contexts.wallet.send.utils :as send-utils]
[taoensso.timbre :as log]
[utils.address :as address]
@@ -24,45 +25,71 @@
(rf/reg-event-fx :wallet/suggested-routes-success
(fn [{:keys [db]} [suggested-routes timestamp]]
(when (= (get-in db [:wallet :ui :send :suggested-routes-call-timestamp]) timestamp)
(let [suggested-routes-data (cske/transform-keys transforms/->kebab-case-keyword suggested-routes)
chosen-route (:best suggested-routes-data)]
(let [suggested-routes-data (cske/transform-keys transforms/->kebab-case-keyword
suggested-routes)
chosen-route (:best suggested-routes-data)
token (get-in db [:wallet :ui :send :token])
collectible (get-in db [:wallet :ui :send :collectible])
token-display-name (get-in db [:wallet :ui :send :token-display-name])
token-decimals (if collectible 0 (:decimals token))
native-token? (and token (= token-display-name "ETH"))
from-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route
:token-decimals
token-decimals
:native-token?
native-token?
:to? false})
from-network-values-for-ui (send-utils/network-values-for-ui from-network-amounts-by-chain)
to-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route
:token-decimals
token-decimals
:native-token?
native-token?
:to? true})
to-network-values-for-ui (send-utils/network-values-for-ui to-network-amounts-by-chain)]
{:db (-> db
(assoc-in [:wallet :ui :send :suggested-routes] suggested-routes-data)
(assoc-in [:wallet :ui :send :route] chosen-route)
(assoc-in [:wallet :ui :send :from-values-by-chain] from-network-values-for-ui)
(assoc-in [:wallet :ui :send :to-values-by-chain] to-network-values-for-ui)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))))
(rf/reg-event-fx :wallet/suggested-routes-error
(fn [{:keys [db]} [_error]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(update-in [:wallet :ui :send] dissoc :suggested-routes :route)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))
(rf/reg-event-fx :wallet/clean-suggested-routes
(fn [{:keys [db]}]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(update-in [:wallet :ui :send] dissoc :loading-suggested-routes?))}))
{:db (update-in db
[:wallet :ui :send]
dissoc
:suggested-routes
:route
:from-values-by-chain
:to-values-by-chain
:loading-suggested-routes?
:suggested-routes-call-timestamp)}))
(rf/reg-event-fx :wallet/clean-send-address
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :recipient :to-address)}))
(rf/reg-event-fx :wallet/clean-disabled-from-networks
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :disabled-from-chain-ids)}))
(rf/reg-event-fx
:wallet/select-send-address
(fn [{:keys [db]} [{:keys [address recipient stack-id start-flow?]}]]
(let [[prefix to-address] (utils/split-prefix-and-address address)
testnet-enabled? (get-in db [:profile/profile :test-networks-enabled?])
goerli-enabled? (get-in db [:profile/profile :is-goerli-enabled?])
prefix-seq (string/split prefix #":")
selected-networks (->> prefix-seq
(remove string/blank?)
(mapv
#(utils/network->chain-id
{:network %
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})))]
selected-networks (network-utils/resolve-receiver-networks
{:prefix prefix
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})]
{:db (-> db
(assoc-in [:wallet :ui :send :recipient] (or recipient address))
(assoc-in [:wallet :ui :send :to-address] to-address)
@@ -84,7 +111,8 @@
(fn [{:keys [db]} [{:keys [token stack-id start-flow?]}]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :collectible)
(assoc-in [:wallet :ui :send :token] token))
(assoc-in [:wallet :ui :send :token] token)
(assoc-in [:wallet :ui :send :token-display-name] (:symbol token)))
:fx [[:dispatch [:wallet/clean-suggested-routes]]
[:dispatch
[:wallet/wizard-navigate-forward
@@ -95,13 +123,15 @@
(rf/reg-event-fx
:wallet/edit-token-to-send
(fn [{:keys [db]} [token]]
{:db (assoc-in db [:wallet :ui :send :token] token)
{:db (-> db
(assoc-in [:wallet :ui :send :token] token)
(assoc-in [:wallet :ui :send :token-display-name] token))
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:wallet/clean-suggested-routes]]]}))
(rf/reg-event-fx :wallet/clean-selected-token
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :token :tx-type)}))
{:db (update-in db [:wallet :ui :send] dissoc :token :token-display-name :tx-type)}))
(rf/reg-event-fx :wallet/clean-selected-collectible
(fn [{:keys [db]}]
@@ -110,18 +140,30 @@
[:wallet :ui :send]
dissoc
:collectible
:token-display-name
:amount
(when (= transaction-type :collecible) :tx-type))})))
(rf/reg-event-fx :wallet/send-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id amount]}]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :token)
(assoc-in [:wallet :ui :send :collectible] collectible)
(assoc-in [:wallet :ui :send :tx-type] :collectible)
(assoc-in [:wallet :ui :send :amount] amount))
:fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]
[:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]}))
(let [collection-data (:collection-data collectible)
collectible-data (:collectible-data collectible)
collectible-id (get-in collectible [:id :token-id])
token-display-name (cond
(and collectible
(not (string/blank? (:name collectible-data))))
(:name collectible-data)
collectible
(str (:name collection-data) " #" collectible-id))]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :token)
(assoc-in [:wallet :ui :send :collectible] collectible)
(assoc-in [:wallet :ui :send :token-display-name] token-display-name)
(assoc-in [:wallet :ui :send :tx-type] :collectible)
(assoc-in [:wallet :ui :send :amount] amount))
:fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]
[:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]})))
(rf/reg-event-fx :wallet/select-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id]}]]
@@ -140,6 +182,12 @@
:start-flow? start-flow?
:flow-id :wallet-flow}]]]}))
(rf/reg-event-fx :wallet/disable-from-networks
(fn [{:keys [db]} [chain-ids]]
{:db (-> db
(assoc-in [:wallet :ui :send :disabled-from-chain-ids] chain-ids)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] true))}))
(rf/reg-event-fx :wallet/get-suggested-routes
(fn [{:keys [db now]} [{:keys [amount]}]]
(let [wallet-address (get-in db [:wallet :current-viewing-account-address])
@@ -147,6 +195,7 @@
transaction-type (get-in db [:wallet :ui :send :tx-type])
collectible (get-in db [:wallet :ui :send :collectible])
to-address (get-in db [:wallet :ui :send :to-address])
disabled-from-chain-ids (or (get-in db [:wallet :ui :send :disabled-from-chain-ids]) [])
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
networks ((if test-networks-enabled? :test :prod)
(get-in db [:wallet :networks]))
@@ -163,7 +212,7 @@
gas-rates constants/gas-rate-medium
amount-in (send-utils/amount-in-hex amount (if token token-decimal 0))
from-address wallet-address
disabled-from-chain-ids []
disabled-from-chain-ids disabled-from-chain-ids
disabled-to-chain-ids (if (= transaction-type :bridge)
(filter #(not= % bridge-to-chain-id) network-chain-ids)
[])
@@ -218,6 +267,7 @@
{:fx [[:dispatch [:wallet/clean-scanned-address]]
[:dispatch [:wallet/clean-local-suggestions]]
[:dispatch [:wallet/clean-send-address]]
[:dispatch [:wallet/clean-disabled-from-networks]]
[:dispatch [:wallet/select-address-tab nil]]
[:dispatch [:dismiss-modal :screen/wallet.transaction-progress]]]}))
@@ -5,60 +5,75 @@
[status-im.contexts.wallet.send.input-amount.view :as input-amount]
[test-helpers.component :as h]
[utils.debounce :as debounce]
[utils.money :as money]
[utils.re-frame :as rf]))
(set! rf/dispatch #())
(set! debounce/debounce-and-dispatch #())
(def sub-mocks
{:profile/profile {:currency :usd}
:wallet/selected-network-details [{:source 525
:short-name "eth"
:network-name :mainnet
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:mainnet :arbitrum
:optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:wallet/wallet-send-token {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route [{:from {:chainid 1
:native-currency-symbol "ETH"}
:to {:chain-id 1
:native-currency-symbol "ETH"}
:gas-amount "23487"
:gas-fees {:base-fee "32.325296406"
:max-priority-fee-per-gas "0.011000001"
:eip1559-enabled true}}]
:wallet/wallet-send-suggested-routes {:candidates []}
:wallet/wallet-send-selected-networks []
:view-id :screen/wallet.send-input-amount
:wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064"
:profile/currency-symbol "$"
:wallet/token-by-symbol {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}})
{:profile/profile {:currency :usd}
:wallet/selected-network-details [{:source 525
:short-name "eth"
:network-name :mainnet
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:mainnet :arbitrum
:optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:wallet/wallet-send-token {:symbol :eth
:networks [{:source 879
:short-name "eth"
:network-name :mainnet
:abbreviated-name "Eth."
:chain-id 1
:related-chain-id 1
:layer 1}]}
:wallet/current-viewing-account-tokens-filtered {:balances-per-chain {1 {:raw-balance
(money/bignumber
"2500")
:has-error false}}
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route [{:from {:chainid 1
:native-currency-symbol "ETH"}
:to {:chain-id 1
:native-currency-symbol "ETH"}
:gas-amount "23487"
:gas-fees {:base-fee "32.325296406"
:max-priority-fee-per-gas "0.011000001"
:eip1559-enabled true}}]
:wallet/wallet-send-suggested-routes {:candidates []}
:wallet/wallet-send-selected-networks [1]
:view-id :screen/wallet.send-input-amount
:wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064"
:profile/currency-symbol "$"
:wallet/token-by-symbol {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-disabled-from-chain-ids []
:wallet/wallet-send-from-values-by-chain {1 (money/bignumber "250")}
:wallet/wallet-send-to-values-by-chain {1 (money/bignumber "250")}})
(h/describe "Send > input amount screen"
(h/setup-restorable-re-frame)
@@ -190,8 +190,9 @@
(reset-input-error num-value current-limit-amount input-error)
(reagent/flush))))
on-navigate-back on-navigate-back
fetch-routes (fn [input-num-value current-limit-amount]
(let [nav-current-screen-id (rf/sub [:view-id])]
fetch-routes (fn [input-num-value current-limit-amount bounce-duration-ms]
(let [nav-current-screen-id (rf/sub [:view-id])
input-num-value (or input-num-value 0)]
; this check is to prevent effect being triggered when screen is
; loaded but not being shown to the user (deep in the navigation
; stack) and avoid undesired behaviors
@@ -201,7 +202,7 @@
(> input-num-value current-limit-amount))
(debounce/debounce-and-dispatch
[:wallet/get-suggested-routes {:amount @input-value}]
2000)
bounce-duration-ms)
(rf/dispatch [:wallet/clean-suggested-routes])))))
handle-on-confirm (fn []
(rf/dispatch [:wallet/send-select-amount
@@ -215,65 +216,92 @@
(reset! input-selection selection)
(reagent/flush))]
(fn []
(let [{fiat-currency :currency} (rf/sub [:profile/profile])
{token-balance :total-balance
token-symbol :symbol
token-networks :networks
:as token} (rf/sub [:wallet/wallet-send-token])
conversion-rate (-> token :market-values-per-currency :usd :price)
loading-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
best-routes (when suggested-routes (or (:best suggested-routes) []))
route (rf/sub [:wallet/wallet-send-route])
to-address (rf/sub [:wallet/wallet-send-to-address])
on-confirm (or default-on-confirm handle-on-confirm)
crypto-decimals (or default-crypto-decimals
(utils/get-crypto-decimals-count token))
crypto-limit (or default-limit-crypto
(utils/get-standard-crypto-format token token-balance))
fiat-limit (.toFixed (* token-balance conversion-rate) 2)
current-limit #(if @crypto-currency? crypto-limit fiat-limit)
current-currency (if @crypto-currency? token-symbol fiat-currency)
limit-label (make-limit-label {:amount (current-limit)
:currency current-currency})
input-num-value (parse-double @input-value)
confirm-disabled? (or (nil? route)
(empty? route)
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (current-limit)))
amount-text (str @input-value " " token-symbol)
native-currency-symbol (when-not confirm-disabled?
(get-in route [:from :native-currency-symbol]))
native-token (when native-currency-symbol
(rf/sub [:wallet/token-by-symbol native-currency-symbol]))
fee-in-native-token (when-not confirm-disabled?
(send-utils/calculate-full-route-gas-fee route))
fee-in-crypto-formatted (when fee-in-native-token
(utils/get-standard-crypto-format native-token
fee-in-native-token))
fee-in-fiat (when-not confirm-disabled?
(utils/calculate-token-fiat-value
{:currency fiat-currency
:balance fee-in-native-token
:token native-token}))
currency-symbol (rf/sub [:profile/currency-symbol])
fee-formatted (when fee-in-fiat
(utils/get-standard-fiat-format fee-in-crypto-formatted
currency-symbol
fee-in-fiat))
show-select-asset-sheet #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[select-asset-bottom-sheet clear-input!])}])]
(let [{fiat-currency :currency} (rf/sub [:profile/profile])
{token-symbol :symbol
token-networks :networks} (rf/sub [:wallet/wallet-send-token])
{token-balance :total-balance
token-balances-per-chain :balances-per-chain
:as
token} (rf/sub
[:wallet/current-viewing-account-tokens-filtered
(str token-symbol)])
conversion-rate (-> token :market-values-per-currency :usd :price)
loading-routes? (rf/sub
[:wallet/wallet-send-loading-suggested-routes?])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
best-routes (when suggested-routes
(or (:best suggested-routes) []))
route (rf/sub [:wallet/wallet-send-route])
to-address (rf/sub [:wallet/wallet-send-to-address])
disabled-from-chain-ids (rf/sub
[:wallet/wallet-send-disabled-from-chain-ids])
from-values-by-chain (rf/sub [:wallet/wallet-send-from-values-by-chain])
to-values-by-chain (rf/sub [:wallet/wallet-send-to-values-by-chain])
on-confirm (or default-on-confirm handle-on-confirm)
crypto-decimals (or default-crypto-decimals
(utils/get-crypto-decimals-count token))
crypto-limit (or default-limit-crypto
(utils/get-standard-crypto-format
token
token-balance))
fiat-limit (.toFixed (* token-balance conversion-rate) 2)
current-limit #(if @crypto-currency? crypto-limit fiat-limit)
current-currency (if @crypto-currency? token-symbol fiat-currency)
limit-label (make-limit-label {:amount (current-limit)
:currency current-currency})
input-num-value (parse-double @input-value)
confirm-disabled? (or (nil? route)
(empty? route)
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (current-limit)))
amount-text (str @input-value " " token-symbol)
native-currency-symbol (when-not confirm-disabled?
(get-in route [:from :native-currency-symbol]))
native-token (when native-currency-symbol
(rf/sub [:wallet/token-by-symbol
native-currency-symbol]))
fee-in-native-token (when-not confirm-disabled?
(send-utils/calculate-full-route-gas-fee route))
fee-in-crypto-formatted (when fee-in-native-token
(utils/get-standard-crypto-format
native-token
fee-in-native-token))
fee-in-fiat (when-not confirm-disabled?
(utils/calculate-token-fiat-value
{:currency fiat-currency
:balance fee-in-native-token
:token native-token}))
currency-symbol (rf/sub [:profile/currency-symbol])
fee-formatted (when fee-in-fiat
(utils/get-standard-fiat-format
fee-in-crypto-formatted
currency-symbol
fee-in-fiat))
show-select-asset-sheet #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[select-asset-bottom-sheet
clear-input!])}])
selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
affordable-networks (send-utils/find-affordable-networks
{:balances-per-chain token-balances-per-chain
:input-value @input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-from-chain-ids})]
(rn/use-mount
(fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
#(.remove app-keyboard-listener))))
(rn/use-effect
#(fetch-routes input-num-value (current-limit))
#(when (> (count affordable-networks) 0)
(fetch-routes input-num-value (current-limit) 2000))
[@input-value])
(rn/use-effect
#(when (> (count affordable-networks) 0)
(fetch-routes input-num-value (current-limit) 0))
[disabled-from-chain-ids])
[rn/view
{:style style/screen
:accessibility-label (str "container" (when @input-error "-error"))}
@@ -303,11 +331,28 @@
:limit-crypto crypto-limit})
:on-token-press show-select-asset-sheet}]
[routes/view
{:amount amount-text
:routes best-routes
:token token
:input-value @input-value
:fetch-routes #(fetch-routes % (current-limit))}]
{:from-values-by-chain from-values-by-chain
:to-values-by-chain to-values-by-chain
:affordable-networks affordable-networks
:routes best-routes
:token token
:input-value @input-value
:fetch-routes #(fetch-routes % (current-limit) 2000)
:disabled-from-networks disabled-from-chain-ids
:on-press-from-network (fn [chain-id _]
(let [disabled-chain-ids (if (contains? (set
disabled-from-chain-ids)
chain-id)
(vec (remove #(= % chain-id)
disabled-from-chain-ids))
(conj disabled-from-chain-ids
chain-id))
re-enabling-chain? (< (count disabled-chain-ids)
(count disabled-from-chain-ids))]
(when (or re-enabling-chain?
(> (count affordable-networks) 1))
(rf/dispatch [:wallet/disable-from-networks
disabled-chain-ids]))))}]
(when (or loading-routes? (seq route))
[estimated-fees
{:loading-suggested-routes? loading-routes?
@@ -12,20 +12,23 @@
{:flex-direction :row
:justify-content :space-between})
(def routes-inner-container
{:margin-top 8
(defn routes-inner-container
[first-item?]
{:margin-top (if first-item? 7.5 11)
:flex-direction :row
:align-items :center
:justify-content :space-between})
(defn section-label
[margin-left]
{:flex 0.5
:margin-left margin-left})
(def section-label-right
{:width 135})
(def section-label-left
{:width 136})
(def network-link
{:right 6
:z-index 1})
{:margin-horizontal -1.5
:z-index 1
:flex 1})
(def empty-container
{:flex-grow 1
@@ -33,9 +36,8 @@
:justify-content :center})
(def add-network
{:margin-top 8
:align-self :flex-end
:right 12})
{:margin-top 11
:align-self :flex-end})
(defn warning-container
[color theme]
@@ -7,19 +7,17 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.send.routes.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.vector :as vector-utils]))
(defn- find-affordable-networks
[{:keys [balances-per-chain]} input-value selected-networks]
(->> balances-per-chain
(filter (fn [[_ {:keys [balance chain-id]}]]
(and
(>= (js/parseFloat balance) input-value)
(some #(= % chain-id) selected-networks))))
(map first)))
(def ^:private network-priority-score
{:ethereum 1
:optimism 2
:arbitrum 3})
(defn- make-network-item
[{:keys [network-name chain-id] :as _network}
@@ -34,6 +32,36 @@
:checked? (some #(= % chain-id) @network-preferences)
:on-change on-change}})
(defn- find-network-link-insertion-index
[network-links chain-id loading-suggested-routes?]
(let [network (utils/id->network chain-id)
inserted-network-link-priority-score (network-priority-score network)]
(or (->> network-links
(keep-indexed (fn [idx network-link]
(let [network-link (utils/id->network (if loading-suggested-routes?
network-link
(get-in network-link
[:from :chain-id])))]
(when (> (network-priority-score network-link)
inserted-network-link-priority-score)
idx))))
first)
(count network-links))))
(defn- add-disabled-networks
[network-links disabled-from-networks loading-suggested-routes?]
(let [sorted-networks (sort-by (comp network-priority-score utils/id->network) disabled-from-networks)]
(reduce (fn [acc-network-links chain-id]
(let [index (find-network-link-insertion-index acc-network-links
chain-id
loading-suggested-routes?)
disabled-network-link {:status :disabled
:chain-id chain-id
:network (utils/id->network chain-id)}]
(vector-utils/insert-element-at acc-network-links disabled-network-link index)))
network-links
sorted-networks)))
(defn networks-drawer
[{:keys [fetch-routes theme]}]
(let [network-details (rf/sub [:wallet/network-details])
@@ -90,7 +118,9 @@
:customization-color color}}]])))
(defn route-item
[{:keys [amount from-network to-network status theme fetch-routes]}]
[{:keys [first-item? from-amount to-amount token-symbol from-chain-id to-chain-id from-network
to-network on-press-from-network on-press-to-network status theme fetch-routes disabled?
loading?]}]
(if (= status :add)
[quo/network-bridge
{:status :add
@@ -99,59 +129,115 @@
{:content (fn [] [networks-drawer
{:theme theme
:fetch-routes fetch-routes}])}])}]
[rn/view {:style style/routes-inner-container}
[rn/view {:style (style/routes-inner-container first-item?)}
[quo/network-bridge
{:amount amount
:network from-network
:status status}]
{:amount (str from-amount " " token-symbol)
:network from-network
:status status
:on-press #(when (and on-press-from-network (not loading?))
(on-press-from-network from-chain-id from-amount))}]
(if (= status :default)
[quo/network-link
{:shape :linear
:source from-network
:destination to-network
:container-style style/network-link}]
[rn/view {:style {:width 73}}])
[rn/view {:style {:flex 1}}])
[quo/network-bridge
{:amount amount
:network to-network
:status status
:container-style {:right 12}}]]))
{:amount (str to-amount " " token-symbol)
:network to-network
:status (cond
(and disabled? loading?) :loading
(and disabled? (not loading?)) :default
:else status)
:on-press #(when (and on-press-to-network (not loading?))
(on-press-to-network to-chain-id to-amount))}]]))
(defn- render-network-link
[item index _
{:keys [from-values-by-chain to-values-by-chain theme fetch-routes on-press-from-network
on-press-to-network token-symbol loading-suggested-routes?]}]
(let [first-item? (zero? index)
disabled-network? (= (:status item) :disabled)
from-chain-id (get-in item [:from :chain-id])
to-chain-id (get-in item [:to :chain-id])
from-amount (when from-chain-id
(from-values-by-chain from-chain-id))
to-amount (when to-chain-id
(to-values-by-chain to-chain-id))]
[route-item
{:first-item? first-item?
:from-amount (if disabled-network? 0 from-amount)
:to-amount (if disabled-network? 0 to-amount)
:token-symbol token-symbol
:disabled? disabled-network?
:loading? loading-suggested-routes?
:theme theme
:fetch-routes fetch-routes
:status (cond
(= (:status item) :add) :add
(= (:status item) :disabled) :disabled
loading-suggested-routes? :loading
:else :default)
:from-chain-id (or from-chain-id (:chain-id item))
:to-chain-id (or to-chain-id (:chain-id item))
:from-network (cond (and loading-suggested-routes?
(not disabled-network?))
(utils/id->network item)
disabled-network?
(utils/id->network (:chain-id
item))
:else
(utils/id->network from-chain-id))
:to-network (cond (and loading-suggested-routes?
(not disabled-network?))
(utils/id->network item)
disabled-network?
(utils/id->network (:chain-id
item))
:else
(utils/id->network to-chain-id))
:on-press-from-network on-press-from-network
:on-press-to-network on-press-to-network}]))
(defn- view-internal
[{:keys [amount routes token input-value theme fetch-routes]}]
(let [selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
loading-networks (find-affordable-networks token input-value selected-networks)
[{:keys [from-values-by-chain to-values-by-chain routes token theme fetch-routes
affordable-networks disabled-from-networks on-press-from-network on-press-to-network]}]
(let [token-symbol (:symbol token)
loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
data (if loading-suggested-routes? loading-networks routes)]
(if (or (and (not-empty loading-networks) loading-suggested-routes?) (not-empty routes))
[rn/flat-list
{:data (if (and (< (count data) 3) (pos? (count data)))
(concat data [{:status :add}])
data)
:content-container-style style/routes-container
:header [rn/view {:style style/routes-header-container}
[quo/section-label
{:section (i18n/label :t/from-label)
:container-style (style/section-label 0)}]
[quo/section-label
{:section (i18n/label :t/to-label)
:container-style (style/section-label 64)}]]
:render-fn (fn [item]
[route-item
{:amount amount
:theme theme
:fetch-routes fetch-routes
:status (cond
(= (:status item) :add) :add
loading-suggested-routes? :loading
:else :default)
:from-network (if loading-suggested-routes?
(utils/id->network item)
(utils/id->network (get-in item [:from :chain-id])))
:to-network (if loading-suggested-routes?
(utils/id->network item)
(utils/id->network (get-in item
[:to :chain-id])))}])}]
network-links (if loading-suggested-routes? affordable-networks routes)]
(if (or (and (not-empty affordable-networks) loading-suggested-routes?) (not-empty routes))
(let [initial-network-links-count (count network-links)
disabled-count (count disabled-from-networks)
network-links (if (not-empty disabled-from-networks)
(add-disabled-networks network-links
disabled-from-networks
loading-suggested-routes?)
network-links)
network-links-with-add-button (if (and (< (- (count network-links) disabled-count)
constants/default-network-count)
(pos? initial-network-links-count))
(concat network-links [{:status :add}])
network-links)]
[rn/flat-list
{:data network-links-with-add-button
:content-container-style style/routes-container
:header [rn/view {:style style/routes-header-container}
[quo/section-label
{:section (i18n/label :t/from-label)
:container-style style/section-label-left}]
[quo/section-label
{:section (i18n/label :t/to-label)
:container-style style/section-label-right}]]
:render-data {:from-values-by-chain from-values-by-chain
:to-values-by-chain to-values-by-chain
:theme theme
:fetch-routes fetch-routes
:on-press-from-network on-press-from-network
:on-press-to-network on-press-to-network
:token-symbol token-symbol
:loading-suggested-routes? loading-suggested-routes?}
:render-fn render-network-link}])
[rn/view {:style style/empty-container}
(when (and (not (nil? routes)) (not loading-suggested-routes?))
[quo/text (i18n/label :t/no-routes-found)])])))
@@ -132,6 +132,7 @@
(rf/dispatch [:wallet/clean-selected-token])
(rf/dispatch [:wallet/clean-selected-collectible])
(rf/dispatch [:wallet/clean-send-address])
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:wallet/select-address-tab nil])
(rf/dispatch [:navigate-back]))
on-change-tab #(rf/dispatch [:wallet/select-address-tab %])
@@ -10,6 +10,8 @@
[input-amount/view
{:current-screen-id :screen/wallet.send-input-amount
:button-one-label (i18n/label :t/confirm)
:on-navigate-back #(rf/dispatch [:navigate-back])}])
:on-navigate-back (fn []
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:navigate-back]))}])
(def view (quo.theme/with-theme view-internal))
@@ -1,23 +1,21 @@
(ns status-im.contexts.wallet.send.transaction-confirmation.view
(:require
[clojure.string :as string]
[legacy.status-im.utils.hex :as utils.hex]
[legacy.status-im.utils.utils :as utils]
[native-module.core :as native-module]
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.floating-button-page.view :as floating-button-page]
[status-im.common.standard-authentication.core :as standard-auth]
[status-im.contexts.wallet.common.utils :as wallet-utils]
[status-im.contexts.wallet.send.transaction-confirmation.style :as style]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn- transaction-title
[{:keys [token-symbol amount account to-address route to-network image-url transaction-type
[{:keys [token-display-name amount account to-address route to-network image-url transaction-type
collectible?]}]
(let [to-network-name (:network-name to-network)
to-network-color (if (= to-network-name :mainnet) :ethereum to-network-name)]
@@ -32,8 +30,8 @@
(i18n/label :t/bridge)
(i18n/label :t/send))]
[quo/summary-tag
{:token (if collectible? "" token-symbol)
:label (str amount " " token-symbol)
{:token (if collectible? "" token-display-name)
:label (str amount " " token-display-name)
:type (if collectible? :collectible :token)
:image-source (if collectible? image-url :eth)}]]
(if (= transaction-type :bridge)
@@ -125,84 +123,34 @@
:emoji (:emoji account)
:customization-color (:color account)}]])]))
(defn network-name-from-chain-id
[chain-id]
(let [network-name (-> (rf/sub [:wallet/network-details-by-chain-id chain-id])
:network-name)]
(if (= network-name :mainnet) :ethereum network-name)))
(defn- network-amounts-from-route
[{:keys [route token-symbol token-decimals to?]}]
(reduce (fn [acc path]
(let [network (if to? (:to path) (:from path))
chain-id (:chain-id network)
amount-hex (if to? (:amount-in path) (:amount-out path))
amount-units (native-module/hex-to-number
(utils.hex/normalize-hex amount-hex))
amount (money/with-precision
(if (= token-symbol "ETH")
(money/wei->ether amount-units)
(money/token->unit amount-units
token-decimals))
6)
network-name (network-name-from-chain-id chain-id)]
(merge-with money/add acc {network-name amount})))
{}
route))
(defn- network-values-from-amounts
[network-amounts token-symbol]
(reduce-kv (fn [acc k v]
(assoc acc
k
{:amount v
:token-symbol token-symbol}))
{}
network-amounts))
(defn- sanitize-network-values
[network-values]
(into {}
(map (fn [[k v]]
[k
(if (money/equal-to (v :amount) 0)
(assoc v :amount "<0.01")
v)])
network-values)))
(defn- values-by-network
[{:keys [collectible amount token-symbol route token-decimals to?]}]
(if collectible
(let [collectible-chain-id (get-in collectible [:id :contract-id :chain-id])
network-name (network-name-from-chain-id collectible-chain-id)]
{network-name {:amount amount :token-symbol token-symbol}})
(let [network-amounts (network-amounts-from-route {:route route
:token-symbol token-symbol
:token-decimals token-decimals
:to? to?})
network-values (network-values-from-amounts network-amounts token-symbol)]
(sanitize-network-values network-values))))
(defn- user-summary
[{:keys [account-props theme label accessibility-label
summary-type network-values]
:as _props}]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label accessibility-label}
label]
[quo/summary-info
{:type summary-type
:networks? true
:values network-values
:account-props account-props}]])
[{:keys [network-values token-display-name account-props theme label accessibility-label
summary-type]}]
(let [network-values
(reduce-kv
(fn [acc chain-id amount]
(let [network-name (wallet-utils/id->network chain-id)]
(assoc acc
(if (= network-name :mainnet) :ethereum network-name)
{:amount amount :token-symbol token-display-name})))
{}
network-values)]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label accessibility-label}
label]
[quo/summary-info
{:type summary-type
:networks? true
:values network-values
:account-props account-props}]]))
(defn data-item
(defn- data-item
[{:keys [title subtitle]}]
[quo/data-item
{:container-style style/detail-item
@@ -217,7 +165,8 @@
:subtitle subtitle}])
(defn- transaction-details
[{:keys [estimated-time-min max-fees token-symbol amount to-address to-network route transaction-type
[{:keys [estimated-time-min max-fees token-display-name amount to-address to-network route
transaction-type
theme]}]
(let [currency-symbol (rf/sub [:profile/currency-symbol])
route-loaded? (and route (seq route))
@@ -253,56 +202,42 @@
(i18n/label :t/bridged-to
{:network (:abbreviated-name to-network)})
(i18n/label :t/user-gets {:name (utils/get-shortened-address to-address)}))
:subtitle (str amount " " token-symbol)}]]
:subtitle (str amount " " token-display-name)}]]
:else
[quo/text {:style {:align-self :center}}
(i18n/label :t/no-routes-found-confirmation)])]]))
(defn collectible-token-symbol
[collectible]
(let [collection-data (:collection-data collectible)
collectible-data (:collectible-data collectible)
collectible-id (get-in collectible [:id :token-id])]
(first (remove
string/blank?
[(:name collectible-data)
(str (:name collection-data) " #" collectible-id)]))))
(defn- view-internal
[_]
(let [on-close (fn []
(rf/dispatch [:wallet/clean-suggested-routes])
(rf/dispatch [:navigate-back]))]
(fn [{:keys [theme]}]
(let [send-transaction-data (rf/sub [:wallet/wallet-send])
{:keys [token collectible amount route
to-address bridge-to-chain-id]} send-transaction-data
collectible? (some? collectible)
token-symbol (if collectible
(collectible-token-symbol collectible)
(:symbol token))
token-decimals (if collectible 0 (:decimals token))
image-url (when collectible
(get-in collectible [:preview-url :uri]))
transaction-type (:tx-type send-transaction-data)
estimated-time-min (reduce + (map :estimated-time route))
max-fees "-"
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
bridge-to-network (when bridge-to-chain-id
(rf/sub [:wallet/network-details-by-chain-id
bridge-to-chain-id]))
from-account-props {:customization-color account-color
:size 32
:emoji (:emoji account)
:type :default
:name (:name account)
:address (utils/get-shortened-address
(:address
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address
to-address)}]
(let [send-transaction-data (rf/sub [:wallet/wallet-send])
{:keys [token-display-name collectible amount route
to-address bridge-to-chain-id
from-values-by-chain
to-values-by-chain]} send-transaction-data
collectible? (some? collectible)
image-url (when collectible
(get-in collectible [:preview-url :uri]))
transaction-type (:tx-type send-transaction-data)
estimated-time-min (reduce + (map :estimated-time route))
max-fees "-"
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
bridge-to-network (when bridge-to-chain-id
(rf/sub [:wallet/network-details-by-chain-id
bridge-to-chain-id]))
from-account-props {:customization-color account-color
:size 32
:emoji (:emoji account)
:type :default
:name (:name account)
:address (utils/get-shortened-address (:address
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address to-address)}]
[rn/view {:style {:flex 1}}
[floating-button-page/view
{:footer-container-padding 0
@@ -329,29 +264,26 @@
:customization-color (:color account)}
[rn/view
[transaction-title
{:token-symbol token-symbol
:amount amount
:account account
:to-address to-address
:route route
:to-network bridge-to-network
:image-url image-url
:transaction-type transaction-type
:collectible? collectible?}]
{:token-display-name token-display-name
:amount amount
:account account
:to-address to-address
:route route
:to-network bridge-to-network
:image-url image-url
:transaction-type transaction-type
:collectible? collectible?}]
[user-summary
{:summary-type :status-account
{:token-display-name token-display-name
:summary-type :status-account
:accessibility-label :summary-from-label
:label (i18n/label :t/from-capitalized)
:network-values from-values-by-chain
:account-props from-account-props
:theme theme
:network-values (values-by-network {:collectible collectible
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? false})}]
:theme theme}]
[user-summary
{:summary-type (if (= transaction-type :bridge)
{:token-display-name token-display-name
:summary-type (if (= transaction-type :bridge)
:status-account
:account)
:accessibility-label :summary-to-label
@@ -359,17 +291,12 @@
:account-props (if (= transaction-type :bridge)
from-account-props
user-props)
:theme theme
:network-values (values-by-network {:collectible collectible
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? true})}]
:network-values to-values-by-chain
:theme theme}]
[transaction-details
{:estimated-time-min estimated-time-min
:max-fees max-fees
:token-symbol token-symbol
:token-display-name token-display-name
:amount amount
:to-address to-address
:to-network bridge-to-network
+28 -1
View File
@@ -1,5 +1,8 @@
(ns status-im.contexts.wallet.send.utils
(:require [utils.money :as money]))
(:require
[legacy.status-im.utils.hex :as utils.hex]
[native-module.core :as native-module]
[utils.money :as money]))
(defn amount-in-hex
[amount token-decimal]
@@ -20,3 +23,27 @@
value1)))
{}
transaction-hashes))
(defn network-amounts-by-chain
[{:keys [route token-decimals native-token? to?]}]
(reduce (fn [acc path]
(let [amount-hex (if to? (:amount-in path) (:amount-out path))
amount-units (native-module/hex-to-number
(utils.hex/normalize-hex amount-hex))
amount (money/with-precision
(if native-token?
(money/wei->ether amount-units)
(money/token->unit amount-units
token-decimals))
6)
chain-id (if to? (get-in path [:to :chain-id]) (get-in path [:from :chain-id]))]
(update acc chain-id money/add amount)))
{}
route))
(defn network-values-for-ui
[amounts]
(reduce-kv (fn [acc k v]
(assoc acc k (if (money/equal-to v 0) "<0.01" v)))
{}
amounts))
@@ -1,6 +1,7 @@
(ns status-im.contexts.wallet.send.utils-test
(:require [cljs.test :refer [deftest is testing]]
[status-im.contexts.wallet.send.utils :as utils]))
[status-im.contexts.wallet.send.utils :as utils]
[utils.money :as money]))
(deftest test-amount-in-hex
(testing "Test amount-in-hex function"
@@ -27,3 +28,68 @@
"0x11" {:status :pending
:id 61
:chain-id :420}})))))
(deftest test-network-amounts-by-chain
(testing "Correctly calculates network amounts for transaction with native token"
(let [route [{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}
{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "2"}}]
token-decimals 18
native-token? true
to? true
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "1")
"2" (money/bignumber "1")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value)))))
(testing
"Correctly calculates network amounts for transaction with native token and multiple routes to same chain-id"
(let [route [{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}
{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}]
token-decimals 18
native-token? true
to? true
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "2")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value)))))
(testing "Correctly calculates network amounts for transaction with non-native token"
(let [route [{:amount-out "0x1e8480"
:from {:chain-id "1"}}
{:amount-out "0x1e8480"
:from {:chain-id "2"}}]
token-decimals 6
native-token? false
to? false
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "2")
"2" (money/bignumber "2")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value))))))
(deftest test-network-values-for-ui
(testing "Sanitizes values correctly for display"
(let [amounts {"1" (money/bignumber "0")
"2" (money/bignumber "2.5")
"3" (money/bignumber "0.005")}
result (utils/network-values-for-ui amounts)
expected {"1" "<0.01"
"2" (money/bignumber "2.5")
"3" (money/bignumber "0.005")}]
(doseq [[chain-id exp-value] expected]
(is #(or (= (get result chain-id) exp-value)
(money/equal-to (get result chain-id) exp-value)))))))
@@ -0,0 +1,5 @@
(ns status-im.contexts.wallet.sheets.buy-token.style)
(def list-container
{:padding-horizontal 8
:padding-bottom 8})
@@ -0,0 +1,15 @@
(ns status-im.contexts.wallet.sheets.buy-token.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.contexts.wallet.common.temp :as temp]
[status-im.contexts.wallet.sheets.buy-token.style :as style]
[utils.i18n :as i18n]))
(defn view
[]
[:<>
[quo/drawer-top {:title (i18n/label :t/buy-tokens)}]
[rn/flat-list
{:data temp/buy-tokens-list
:style style/list-container
:render-fn quo/settings-item}]])
+2
View File
@@ -10,6 +10,7 @@
[status-im.common.json-rpc.events]
status-im.common.log
status-im.common.password-authentication.events
status-im.common.shared-urls.events
status-im.common.signals.events
status-im.common.theme.events
[status-im.common.toasts.events]
@@ -39,6 +40,7 @@
status-im.contexts.wallet.send.events
status-im.contexts.wallet.signals
[status-im.db :as db]
status-im.navigation.effects
status-im.navigation.events
[utils.re-frame :as rf]))
+7 -4
View File
@@ -10,10 +10,13 @@
(defonce ^:private feature-flags-config
(reagent/atom
{::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED)
::wallet.edit-derivation-path (enabled-in-env? :FLAG_EDIT_DERIVATION_PATH)
::wallet.remove-account (enabled-in-env? :FLAG_REMOVE_ACCOUNT_ENABLED)
::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED)}))
{::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED)
::wallet.edit-derivation-path (enabled-in-env? :FLAG_EDIT_DERIVATION_PATH)
::wallet.remove-account (enabled-in-env? :FLAG_REMOVE_ACCOUNT_ENABLED)
::wallet.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED)
::wallet.assets-modal-manage-tokens (enabled-in-env? :FLAG_ASSETS_MODAL_MANAGE_TOKENS)
::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE)
::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED)}))
(defn feature-flags [] @feature-flags-config)
+14
View File
@@ -5,6 +5,7 @@
[react-native.core :as rn]
[react-native.navigation :as navigation]
[react-native.platform :as platform]
[react-native.share :as share]
[status-im.contexts.shell.jump-to.constants :as shell.constants]
[status-im.contexts.shell.jump-to.utils :as jump-to.utils]
[status-im.navigation.options :as options]
@@ -183,6 +184,19 @@
(rf/reg-fx :open-modal-fx open-modal)
;;;; Share
(rf/reg-fx :effects.share/open
(fn [{:keys [options on-success on-error]}]
(cond-> (share/open options)
(fn? on-success) (.then on-success)
:always (.catch (fn [error]
(log/error "Failed to share content"
{:error error
:effect :effects.share/open})
(when (fn? on-error)
(on-error error)))))))
;;;; Overlay
(defn show-overlay
+6
View File
@@ -144,3 +144,9 @@
{:events [:reload-status-nav-color]}
[{:keys [db]} view-id]
{:reload-status-nav-color-fx (or view-id (:view-id db))})
(defn open-share
[_ [config]]
{:fx [[:effects.share/open config]]})
(rf/reg-event-fx :open-share open-share)
+6
View File
@@ -241,6 +241,12 @@
:message-pin-enabled message-pin-enabled
:can-delete-message-for-everyone? can-delete-message-for-everyone?})))
(re-frame/reg-sub
:chats/current-chat-one-to-one?
:<- [:chats/current-raw-chat]
(fn [{:keys [chat-type]}]
(= chat-type constants/one-to-one-chat-type)))
(re-frame/reg-sub
:chats/photo-path
:<- [:contacts/contacts]

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