Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6b19c021f | ||
|
|
a258717e83 | ||
|
|
a8395e51a4 | ||
|
|
db501b5efd | ||
|
|
44732e9736 | ||
|
|
d12c05aab6 | ||
|
|
4134d18cbc | ||
|
|
8600c1d3a5 | ||
|
|
570c62e30e | ||
|
|
549efdb77c | ||
|
|
8807a63989 | ||
|
|
c0be0b7670 | ||
|
|
6e854428f2 | ||
|
|
c81dd8e3e3 | ||
|
|
24f4c4a07e | ||
|
|
98762c74f1 | ||
|
|
f3aeddb53a | ||
|
|
f5fcc9f45e | ||
|
|
9aa9dc1b4c | ||
|
|
61974dae4d | ||
|
|
8b3e639abd | ||
|
|
7c901fa045 | ||
|
|
8a12ad3510 | ||
|
|
c8044cd2f2 | ||
|
|
6872d1a857 | ||
|
|
98d4969ca5 | ||
|
|
5412092ca1 | ||
|
|
a8695116e2 | ||
|
|
76773a4165 | ||
|
|
32fc7b14e8 | ||
|
|
82a8f9f44d | ||
|
|
8df72d0c29 | ||
|
|
794cd57961 | ||
|
|
6bc29088ab |
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
} }
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
} }
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
} }
|
||||
}
|
||||
|
||||
@@ -57,9 +57,6 @@
|
||||
|
||||
[Troubleshooting for known errors](troubleshooting.md)
|
||||
|
||||
## Articles
|
||||
|
||||
[Notes on memoization](./articles/notes-on-memoization.md)
|
||||
|
||||
|
||||
## Outdated:
|
||||
|
||||
@@ -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 React’s Stable Values](https://shopify.engineering/master-reacts-stable-values)
|
||||
@@ -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
@@ -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.
|
||||
|
||||

|
||||
|
||||
### 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
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
@@ -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))]}]]))))
|
||||
|
||||
@@ -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)])
|
||||
@@ -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})
|
||||
@@ -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}]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)))))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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}]
|
||||
]))
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}]])
|
||||
@@ -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]))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user