Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
457767f167 | ||
|
|
bab8208de2 | ||
|
|
434e75e669 | ||
|
|
d3a3f04488 | ||
|
|
eb3f2cb132 | ||
|
|
84eba2dd55 | ||
|
|
6793aa2994 | ||
|
|
043ae742df | ||
|
|
fb2c86b3c8 | ||
|
|
658f62e8ce | ||
|
|
e6b346ca32 | ||
|
|
00b0755a73 | ||
|
|
f8cc85f5e2 | ||
|
|
6ba89d0c6e | ||
|
|
2614131426 |
@@ -57,6 +57,9 @@
|
||||
|
||||
[Troubleshooting for known errors](troubleshooting.md)
|
||||
|
||||
## Articles
|
||||
|
||||
[Notes on memoization](./articles/notes-on-memoization.md)
|
||||
|
||||
|
||||
## Outdated:
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# 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)
|
||||
+41
-22
@@ -1,5 +1,13 @@
|
||||
# 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
|
||||
@@ -49,36 +57,46 @@ 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]
|
||||
> DO NOT USE anon functions directly in the props
|
||||
|
||||
> 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.
|
||||
|
||||
BAD
|
||||
```clojure
|
||||
(defn view
|
||||
[]
|
||||
(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)}]))
|
||||
[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)}]))
|
||||
```
|
||||
|
||||
GOOD:
|
||||
```clojure
|
||||
(defn view
|
||||
[]
|
||||
(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}]))
|
||||
(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}]))
|
||||
```
|
||||
|
||||
## Global state and subscriptions
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -97,7 +115,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`
|
||||
|
||||
@@ -122,7 +140,7 @@ NOW:
|
||||
on-blur (rn/use-callback #(reset! focused? false))]))
|
||||
```
|
||||
|
||||
## Effects
|
||||
### Effects
|
||||
|
||||
LIFECYCLE:
|
||||
|
||||
@@ -170,13 +188,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.
|
||||
|
||||
@@ -198,7 +216,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).
|
||||
|
||||
@@ -252,3 +270,4 @@ 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,6 +4,7 @@
|
||||
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
|
||||
legacy.status-im.communities.e2e
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.contexts.shell.activity-center.events :as activity-center]
|
||||
[status-im.navigation.events :as navigation]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]))
|
||||
@@ -22,60 +23,10 @@
|
||||
{}
|
||||
requests))
|
||||
|
||||
(defn- fetch-community-id-input
|
||||
[{:keys [db]}]
|
||||
(:communities/community-id-input db))
|
||||
|
||||
(rf/defn handle-response
|
||||
[_ response-js]
|
||||
{:dispatch [:sanitize-messages-and-process-response response-js]})
|
||||
|
||||
(rf/defn invite-users
|
||||
{:events [::invite-people-confirmation-pressed]}
|
||||
[cofx user-pk contacts]
|
||||
(let [community-id (fetch-community-id-input cofx)
|
||||
pks (if (seq user-pk)
|
||||
(conj contacts user-pk)
|
||||
contacts)]
|
||||
(when (seq pks)
|
||||
{:json-rpc/call [{:method "wakuext_inviteUsersToCommunity"
|
||||
:params [{:communityId community-id
|
||||
:users pks}]
|
||||
:js-response true
|
||||
:on-success #(re-frame/dispatch [::people-invited %])
|
||||
:on-error #(do
|
||||
(log/error "failed to invite-user community" %)
|
||||
(re-frame/dispatch [::failed-to-invite-people %]))}]})))
|
||||
|
||||
(rf/defn share-community
|
||||
{:events [::share-community-confirmation-pressed]}
|
||||
[cofx user-pk contacts]
|
||||
(let [community-id (fetch-community-id-input cofx)
|
||||
pks (if (seq user-pk)
|
||||
(conj contacts user-pk)
|
||||
contacts)]
|
||||
(when (seq pks)
|
||||
{:json-rpc/call [{:method "wakuext_shareCommunity"
|
||||
:params [{:communityId community-id
|
||||
:users pks}]
|
||||
:js-response true
|
||||
:on-success #(re-frame/dispatch [::people-invited %])
|
||||
:on-error #(do
|
||||
(log/error "failed to invite-user community" %)
|
||||
(re-frame/dispatch [::failed-to-share-community %]))}]})))
|
||||
|
||||
(re-frame/reg-event-fx :communities/invite-people-pressed
|
||||
(fn [{:keys [db]} [id]]
|
||||
{:db (assoc db :communities/community-id-input id)
|
||||
:fx [[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch [:open-modal :legacy-invite-people-community {:invite? true}]]]}))
|
||||
|
||||
(re-frame/reg-event-fx :communities/share-community-pressed
|
||||
(fn [{:keys [db]} [id]]
|
||||
{:db (assoc db :communities/community-id-input id)
|
||||
:fx [[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch [:open-modal :legacy-invite-people-community {}]]]}))
|
||||
|
||||
(rf/defn people-invited
|
||||
{:events [::people-invited]}
|
||||
[cofx response-js]
|
||||
@@ -90,6 +41,14 @@
|
||||
[:sanitize-messages-and-process-response response-js]
|
||||
[:activity-center.notifications/fetch-unread-count]]}))
|
||||
|
||||
(rf/defn member-banned
|
||||
{:events [::member-banned]}
|
||||
[cofx response-js]
|
||||
(rf/merge cofx
|
||||
(bottom-sheet/hide-bottom-sheet-old)
|
||||
(handle-response response-js)
|
||||
(activity-center/notifications-fetch-unread-count)))
|
||||
|
||||
(rf/defn member-ban
|
||||
{:events [::member-ban]}
|
||||
[cofx community-id public-key]
|
||||
|
||||
@@ -13,3 +13,11 @@
|
||||
:background-color (colors/theme-colors (colors/custom-color customization-color 50)
|
||||
(colors/custom-color customization-color 60)
|
||||
theme)})
|
||||
|
||||
(defn avatar-identifier
|
||||
[theme]
|
||||
{:text-align :center
|
||||
:font-size 36
|
||||
:color (colors/theme-colors colors/black
|
||||
colors/white
|
||||
theme)})
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
(ns quo.components.avatars.group-avatar.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.components.avatars.group-avatar.style :as style]
|
||||
[quo.components.icon :as icon]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
@@ -21,7 +23,7 @@
|
||||
|
||||
(defn- view-internal
|
||||
[_]
|
||||
(fn [{:keys [size theme customization-color picture icon-name]
|
||||
(fn [{:keys [size theme customization-color picture icon-name emoji chat-name]
|
||||
:or {size :size-20
|
||||
customization-color :blue
|
||||
picture nil
|
||||
@@ -38,8 +40,27 @@
|
||||
{:source picture
|
||||
:style {:width container-size
|
||||
:height container-size}}]
|
||||
[icon/icon icon-name
|
||||
{:size icon-size
|
||||
:color colors/white-opa-70}])])))
|
||||
(cond
|
||||
emoji
|
||||
(if (= size :size-80)
|
||||
[rn/text
|
||||
{:style (style/avatar-identifier theme)}
|
||||
emoji]
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
:style (dissoc (style/avatar-identifier theme) :font-size)}
|
||||
emoji])
|
||||
chat-name
|
||||
(if (= size :size-80)
|
||||
[rn/text
|
||||
{:style (style/avatar-identifier theme)}
|
||||
((comp first string/upper-case) chat-name)]
|
||||
[text/text
|
||||
{:size :paragraph-1}
|
||||
((comp first string/upper-case) chat-name)])
|
||||
:else
|
||||
[icon/icon icon-name
|
||||
{:size icon-size
|
||||
:color colors/white-opa-70}]))])))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
(ns quo.components.inputs.input.style
|
||||
(:require
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.colors :as colors]))
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.platform :as platform]))
|
||||
|
||||
(defn variants-colors
|
||||
[blur? theme]
|
||||
@@ -97,9 +98,12 @@
|
||||
(assoc base-props
|
||||
:text-align-vertical :top
|
||||
:line-height 22)
|
||||
(assoc base-props
|
||||
:height (if small? 30 38)
|
||||
:line-height nil))))
|
||||
(cond-> base-props
|
||||
:always
|
||||
(assoc :height (if small? 30 38)
|
||||
:line-height nil)
|
||||
platform/ios?
|
||||
(assoc :padding-top (+ padding 2))))))
|
||||
|
||||
(defn right-icon-touchable-area
|
||||
[small?]
|
||||
|
||||
@@ -41,21 +41,24 @@
|
||||
(defn user
|
||||
[{:keys [short-chat-key primary-name secondary-name photo-path online? contact? verified?
|
||||
untrustworthy? on-press on-long-press accessory customization-color theme
|
||||
allow-multiple-presses?]}]
|
||||
allow-multiple-presses? disabled?]}]
|
||||
[rn/touchable-highlight
|
||||
{:style container-style
|
||||
:underlay-color (colors/resolve-color customization-color theme 5)
|
||||
:allow-multiple-presses? allow-multiple-presses?
|
||||
:accessibility-label :user-list
|
||||
:on-press (when on-press on-press)
|
||||
:on-long-press (when on-long-press on-long-press)}
|
||||
:on-long-press (when on-long-press on-long-press)
|
||||
:disabled disabled?}
|
||||
[:<>
|
||||
[user-avatar/user-avatar
|
||||
{:full-name primary-name
|
||||
:profile-picture photo-path
|
||||
:online? online?
|
||||
:size :small}]
|
||||
[rn/view {:style {:margin-horizontal 8 :flex 1}}
|
||||
[rn/view
|
||||
{:style {:margin-horizontal 8
|
||||
:flex 1}}
|
||||
[author/view
|
||||
{:primary-name primary-name
|
||||
:secondary-name secondary-name
|
||||
@@ -69,4 +72,4 @@
|
||||
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
|
||||
short-chat-key])]
|
||||
(when accessory
|
||||
[action-icon accessory customization-color theme])]])
|
||||
[action-icon accessory customization-color disabled? theme])]])
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
[:map
|
||||
[:type {:optional true} [:enum :default :watch-only :add-account :empty :missing-keypair]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:theme :schema.common/theme]
|
||||
[:metrics? {:optional true} [:maybe :boolean]]
|
||||
[:on-press {:optional true} [:maybe fn?]]])
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.linear-gradient :as linear-gradient]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- loading-view
|
||||
@@ -96,87 +95,87 @@
|
||||
:end {:x 1 :y 0}}])
|
||||
|
||||
(defn- user-account
|
||||
[_]
|
||||
(let [pressed? (reagent/atom false)
|
||||
on-press-in #(reset! pressed? true)
|
||||
on-press-out #(reset! pressed? false)]
|
||||
(fn [{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
|
||||
theme on-press]}]
|
||||
(let [watch-only? (= :watch-only type)
|
||||
missing-keypair? (= :missing-keypair type)]
|
||||
(if loading?
|
||||
[loading-view
|
||||
{:customization-color customization-color
|
||||
:type type
|
||||
:theme theme
|
||||
:metrics? metrics?}]
|
||||
[rn/pressable
|
||||
{:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:style (style/card {:customization-color customization-color
|
||||
:type type
|
||||
:theme theme
|
||||
:pressed? @pressed?
|
||||
:metrics? metrics?})
|
||||
:on-press on-press}
|
||||
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
|
||||
[customization-colors/overlay
|
||||
{:customization-color customization-color
|
||||
:border-radius 16
|
||||
:theme theme
|
||||
:pressed? @pressed?}])
|
||||
[rn/view {:style style/profile-container}
|
||||
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
|
||||
[text/text {:style style/emoji} emoji]]
|
||||
[rn/view {:style style/watch-only-container}
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:number-of-lines 1
|
||||
:max-width 110
|
||||
:margin-right 4
|
||||
:ellipis-mode :tail
|
||||
:style (style/account-name type theme)}
|
||||
name]
|
||||
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
|
||||
(when missing-keypair?
|
||||
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
|
||||
[text/text
|
||||
{:size :heading-2
|
||||
:weight :semi-bold
|
||||
:style (style/account-value type theme)}
|
||||
balance]
|
||||
(when metrics?
|
||||
[rn/view {:style style/metrics-container}
|
||||
[metrics-percentage type theme percentage-value]
|
||||
(when (not= :empty type)
|
||||
[metrics-info type theme amount])])
|
||||
(when watch-only?
|
||||
[gradient-overview theme customization-color])])))))
|
||||
[{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
|
||||
on-press]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))
|
||||
watch-only? (= :watch-only type)
|
||||
missing-keypair? (= :missing-keypair type)]
|
||||
(if loading?
|
||||
[loading-view
|
||||
{:customization-color customization-color
|
||||
:type type
|
||||
:theme theme
|
||||
:metrics? metrics?}]
|
||||
[rn/pressable
|
||||
{:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:style (style/card {:customization-color customization-color
|
||||
:type type
|
||||
:theme theme
|
||||
:pressed? pressed?
|
||||
:metrics? metrics?})
|
||||
:on-press on-press}
|
||||
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
|
||||
[customization-colors/overlay
|
||||
{:customization-color customization-color
|
||||
:border-radius 16
|
||||
:theme theme
|
||||
:pressed? pressed?}])
|
||||
[rn/view {:style style/profile-container}
|
||||
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
|
||||
[text/text {:style style/emoji} emoji]]
|
||||
[rn/view {:style style/watch-only-container}
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:number-of-lines 1
|
||||
:max-width 110
|
||||
:margin-right 4
|
||||
:ellipis-mode :tail
|
||||
:style (style/account-name type theme)}
|
||||
name]
|
||||
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
|
||||
(when missing-keypair?
|
||||
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
|
||||
[text/text
|
||||
{:size :heading-2
|
||||
:weight :semi-bold
|
||||
:style (style/account-value type theme)}
|
||||
balance]
|
||||
(when metrics?
|
||||
[rn/view {:style style/metrics-container}
|
||||
[metrics-percentage type theme percentage-value]
|
||||
(when (not= :empty type)
|
||||
[metrics-info type theme amount])])
|
||||
(when watch-only?
|
||||
[gradient-overview theme customization-color])])))
|
||||
|
||||
(defn- add-account-view
|
||||
[_]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [on-press customization-color theme metrics?]}]
|
||||
[rn/pressable
|
||||
{:on-press on-press
|
||||
:on-press-in #(reset! pressed? true)
|
||||
:on-press-out #(reset! pressed? false)
|
||||
:style (style/add-account-container {:theme theme
|
||||
:metrics? metrics?
|
||||
:pressed? @pressed?})}
|
||||
[button/button
|
||||
{:type :primary
|
||||
:size 24
|
||||
:icon true
|
||||
:accessibility-label :add-account
|
||||
:on-press on-press
|
||||
:pressed? @pressed?
|
||||
:on-press-in #(reset! pressed? true)
|
||||
:on-press-out #(reset! pressed? false)
|
||||
:customization-color customization-color
|
||||
:icon-only? true}
|
||||
:i/add]])))
|
||||
[{:keys [on-press customization-color metrics?]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))]
|
||||
[rn/pressable
|
||||
{:on-press on-press
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:style (style/add-account-container {:theme theme
|
||||
:metrics? metrics?
|
||||
:pressed? pressed?})}
|
||||
[button/button
|
||||
{:on-press on-press
|
||||
:type :primary
|
||||
:size 24
|
||||
:icon true
|
||||
:accessibility-label :add-account
|
||||
:pressed? pressed?
|
||||
:icon-only? true
|
||||
:customization-color customization-color}
|
||||
:i/add]]))
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [type] :as props}]
|
||||
@@ -185,6 +184,4 @@
|
||||
:add-account [add-account-view props]
|
||||
nil))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal component-schema/?schema)))
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
[:props
|
||||
[:map {:closed true}
|
||||
[:status {:optional true} [:maybe [:enum :default :error]]]
|
||||
[:theme :schema.common/theme]
|
||||
[:on-inc-press {:optional true} [:maybe fn?]]
|
||||
[:on-dec-press {:optional true} [:maybe fn?]]
|
||||
[:on-change-text {:optional true} [:maybe fn?]]
|
||||
[:container-style {:optional true} [:maybe :map]]
|
||||
[:min-value {:optional true} [:maybe :int]]
|
||||
[:max-value {:optional true} [:maybe :int]]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.wallet.amount-input.schema :as amount-input.schema]
|
||||
[quo.components.wallet.amount-input.style :as style]
|
||||
[quo.theme :as quo.theme]
|
||||
[quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[schema.core :as schema]))
|
||||
|
||||
@@ -21,35 +21,34 @@
|
||||
icon])
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [on-inc-press on-dec-press theme status value min-value max-value
|
||||
[{:keys [on-inc-press on-dec-press status value min-value max-value
|
||||
container-style]
|
||||
:or {value 0
|
||||
min-value 0
|
||||
max-value 999999999}}]
|
||||
[rn/view
|
||||
{:style (merge style/container container-style)}
|
||||
[amount-button
|
||||
{:theme theme
|
||||
:accessibility-label :amount-input-dec-button
|
||||
:icon :i/remove
|
||||
:on-press on-dec-press
|
||||
:disabled? (>= min-value value)}]
|
||||
[rn/view {:style style/input-container}
|
||||
[text/text
|
||||
{:number-of-lines 1
|
||||
:accessibility-label :amount-input
|
||||
:weight :semi-bold
|
||||
:size :heading-1
|
||||
:align-self :center
|
||||
:style (style/input-text theme (or status :default))}
|
||||
value]]
|
||||
[amount-button
|
||||
{:theme theme
|
||||
:icon :i/add
|
||||
:accessibility-label :amount-input-inc-button
|
||||
:on-press on-inc-press
|
||||
:disabled? (>= value max-value)}]])
|
||||
(let [theme (quo.theme/use-theme-value)]
|
||||
[rn/view
|
||||
{:style (merge style/container container-style)}
|
||||
[amount-button
|
||||
{:theme theme
|
||||
:accessibility-label :amount-input-dec-button
|
||||
:icon :i/remove
|
||||
:on-press on-dec-press
|
||||
:disabled? (>= min-value value)}]
|
||||
[rn/view {:style style/input-container}
|
||||
[text/text
|
||||
{:number-of-lines 1
|
||||
:accessibility-label :amount-input
|
||||
:weight :semi-bold
|
||||
:size :heading-1
|
||||
:align-self :center
|
||||
:style (style/input-text theme (or status :default))}
|
||||
value]]
|
||||
[amount-button
|
||||
{:theme theme
|
||||
:icon :i/add
|
||||
:accessibility-label :amount-input-inc-button
|
||||
:on-press on-inc-press
|
||||
:disabled? (>= value max-value)}]]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal amount-input.schema/?schema)))
|
||||
(def view (schema/instrument #'view-internal amount-input.schema/?schema))
|
||||
|
||||
@@ -37,8 +37,9 @@
|
||||
:border? true}]))
|
||||
|
||||
(defn title-view
|
||||
[{:keys [details action selected? type blur? customization-color on-options-press theme]}]
|
||||
(let [{:keys [full-name]} details]
|
||||
[{:keys [details action selected? type blur? customization-color on-options-press]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
{:keys [full-name]} details]
|
||||
[rn/view
|
||||
{:style style/title-container
|
||||
:accessibility-label :title}
|
||||
@@ -89,7 +90,7 @@
|
||||
[item & _rest]
|
||||
[account-list-card/view item])
|
||||
|
||||
(defn- view-internal
|
||||
(defn view
|
||||
[{:keys [accounts action container-style selected? on-press] :as props}]
|
||||
[rn/pressable
|
||||
{:style (style/container (merge props
|
||||
@@ -108,5 +109,3 @@
|
||||
:render-fn acc-list-card
|
||||
:separator [rn/view {:style {:height 8}}]
|
||||
:style {:padding-horizontal 8}}]])
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
|
||||
@@ -12,6 +12,5 @@
|
||||
[:amount :int]
|
||||
[:max-amount :int]
|
||||
[:network-name [:or :string :keyword]]]]]]
|
||||
[:container-style {:optional true} [:maybe :map]]
|
||||
[:theme :schema.common/theme]]]]
|
||||
[:container-style {:optional true} [:maybe :map]]]]]
|
||||
:any])
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
:background-color (colors/resolve-color network-name nil 10)})
|
||||
|
||||
(defn network-bar
|
||||
[{:keys [max-width on-top? bar-division? theme]
|
||||
[{:keys [bar-max-width on-top? bar-division? theme]
|
||||
{:keys [network-name translate-x-shared-value]} :bar}
|
||||
width-shared-value]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:width width-shared-value
|
||||
:transform [{:translate-x translate-x-shared-value}]}
|
||||
{:max-width max-width
|
||||
{:max-width bar-max-width
|
||||
:flex-direction :row
|
||||
:justify-content :flex-end
|
||||
:background-color (colors/resolve-color network-name nil)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
[react-native.core :as rn]
|
||||
[react-native.gesture :as gesture]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]
|
||||
[utils.number]))
|
||||
|
||||
@@ -22,60 +21,73 @@
|
||||
(js/clearTimeout (k @timeouts))
|
||||
(swap! timeouts assoc k (js/setTimeout exec-fn-and-remove-timeout ms))))
|
||||
|
||||
(defn- f-slider
|
||||
[slider-shared-values]
|
||||
[rn/view {:style style/slider-container}
|
||||
[reanimated/view {:style (style/slider slider-shared-values)}]])
|
||||
(def get-gesture
|
||||
(memoize
|
||||
(fn
|
||||
[[detecting-gesture? slider-width-shared-value max-amount
|
||||
slider-height-shared-value
|
||||
amount-shared-value amount-on-gesture-start width->amount
|
||||
slider-opacity-shared-value on-new-amount set-detecting-gesture]]
|
||||
(-> (gesture/gesture-pan)
|
||||
(gesture/enabled detecting-gesture?)
|
||||
(gesture/on-begin
|
||||
(fn [_]
|
||||
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
|
||||
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
|
||||
(gesture/on-update
|
||||
(fn [event]
|
||||
(let [new-amount (-> (oops/oget event "translationX")
|
||||
(width->amount)
|
||||
(+ @amount-on-gesture-start)
|
||||
(utils.number/value-in-range 1 max-amount))]
|
||||
(reanimated/set-shared-value amount-shared-value new-amount))))
|
||||
(gesture/on-finalize
|
||||
(fn [_]
|
||||
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
|
||||
(animation/hide-slider slider-opacity-shared-value)
|
||||
(on-new-amount (reanimated/get-shared-value amount-shared-value))
|
||||
(add-new-timeout :turn-off-gesture #(set-detecting-gesture false) 20)))))))
|
||||
|
||||
(defn f-network-bar
|
||||
[_]
|
||||
(let [detecting-gesture? (reagent/atom false)
|
||||
amount-on-gesture-start (atom 0)]
|
||||
(fn [{:keys [total-width total-amount on-press on-new-amount allow-press?]
|
||||
{:keys [amount-shared-value
|
||||
max-amount]} :bar
|
||||
:as props}]
|
||||
(let [slider-width-shared-value (reanimated/use-shared-value 4)
|
||||
slider-height-shared-value (reanimated/use-shared-value 32)
|
||||
slider-opacity-shared-value (reanimated/use-shared-value 0)
|
||||
network-bar-shared-value (reanimated/interpolate amount-shared-value
|
||||
[0 total-amount]
|
||||
[0 total-width])
|
||||
width->amount #(/ (* % total-amount) total-width)]
|
||||
[rn/pressable
|
||||
{:on-press (fn []
|
||||
(when (and (not @detecting-gesture?) allow-press?)
|
||||
(on-press)
|
||||
(reset! detecting-gesture? true)
|
||||
(animation/show-slider slider-opacity-shared-value)))}
|
||||
[reanimated/view
|
||||
{:style (style/network-bar props network-bar-shared-value)
|
||||
:accessibility-label :network-routing-bar}
|
||||
[gesture/gesture-detector
|
||||
{:gesture
|
||||
(-> (gesture/gesture-pan)
|
||||
(gesture/enabled @detecting-gesture?)
|
||||
(gesture/on-begin
|
||||
(fn [_]
|
||||
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
|
||||
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
|
||||
(gesture/on-update
|
||||
(fn [event]
|
||||
(let [new-amount (-> (oops/oget event "translationX")
|
||||
(width->amount)
|
||||
(+ @amount-on-gesture-start)
|
||||
(utils.number/value-in-range 1 max-amount))]
|
||||
(reanimated/set-shared-value amount-shared-value new-amount))))
|
||||
(gesture/on-finalize
|
||||
(fn [_]
|
||||
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
|
||||
(animation/hide-slider slider-opacity-shared-value)
|
||||
(on-new-amount (reanimated/get-shared-value amount-shared-value))
|
||||
(add-new-timeout :turn-off-gesture #(reset! detecting-gesture? false) 20))))}
|
||||
[:f> f-slider
|
||||
{:width-shared-value slider-width-shared-value
|
||||
:height-shared-value slider-height-shared-value
|
||||
:opacity-shared-value slider-opacity-shared-value}]]]]))))
|
||||
(defn network-bar
|
||||
[{:keys [total-width total-amount on-press on-new-amount allow-press?
|
||||
bar-idx bar-width bar-max-width]
|
||||
{:keys [amount-shared-value
|
||||
max-amount]
|
||||
:as bar} :bar
|
||||
:as props}]
|
||||
(let [[detecting-gesture?
|
||||
set-detecting-gesture] (rn/use-state false)
|
||||
amount-on-gesture-start (rn/use-ref-atom 0)
|
||||
slider-width-shared-value (reanimated/use-shared-value 4)
|
||||
slider-height-shared-value (reanimated/use-shared-value 32)
|
||||
slider-opacity-shared-value (reanimated/use-shared-value 0)
|
||||
network-bar-shared-value (reanimated/interpolate amount-shared-value
|
||||
[0 total-amount]
|
||||
[0 total-width])
|
||||
width->amount #(/ (* % total-amount) total-width)
|
||||
gesture (get-gesture
|
||||
[detecting-gesture? slider-width-shared-value max-amount
|
||||
slider-height-shared-value
|
||||
amount-shared-value amount-on-gesture-start width->amount
|
||||
slider-opacity-shared-value on-new-amount set-detecting-gesture])
|
||||
on-press (rn/use-callback
|
||||
(fn []
|
||||
(when (and (not detecting-gesture?) allow-press?)
|
||||
(on-press bar bar-idx bar-width bar-max-width)
|
||||
(set-detecting-gesture true)
|
||||
(animation/show-slider slider-opacity-shared-value)))
|
||||
[detecting-gesture? allow-press? on-press])]
|
||||
[rn/pressable
|
||||
{:on-press on-press}
|
||||
[reanimated/view
|
||||
{:style (style/network-bar props network-bar-shared-value)
|
||||
:accessibility-label :network-routing-bar}
|
||||
[gesture/gesture-detector {:gesture gesture}
|
||||
[rn/view {:style style/slider-container}
|
||||
[reanimated/view
|
||||
{:style (style/slider {:width-shared-value slider-width-shared-value
|
||||
:height-shared-value slider-height-shared-value
|
||||
:opacity-shared-value slider-opacity-shared-value})}]]]]]))
|
||||
|
||||
(defn- add-bar-shared-values
|
||||
[{:keys [amount] :as network}]
|
||||
@@ -93,101 +105,113 @@
|
||||
(interleave (repeat [rn/view {:style (style/dashed-line-line network-name)}])
|
||||
(repeat [rn/view {:style style/dashed-line-space}])))))
|
||||
|
||||
(defn f-network-routing-bars
|
||||
[_]
|
||||
(let [selected-network-idx (reagent/atom nil)
|
||||
press-locked? (reagent/atom false)
|
||||
lock-press #(reset! press-locked? true)
|
||||
unlock-press #(reset! press-locked? false)
|
||||
reset-state-values #(reset! selected-network-idx nil)]
|
||||
(fn [{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
|
||||
(let [bar-opacity-shared-value (reanimated/use-shared-value 0)
|
||||
network-bars (map add-bar-shared-values networks)
|
||||
amount->width #(* % (/ total-width total-amount))
|
||||
bars-widths-negative (map #(-> % get-negative-amount amount->width)
|
||||
network-bars)
|
||||
last-bar-idx (dec (count network-bars))]
|
||||
(rn/use-effect
|
||||
#(when (and (not requesting-data?) @selected-network-idx)
|
||||
(let [bar (nth network-bars @selected-network-idx)]
|
||||
(animation/hide-pressed-bar bar amount->width))
|
||||
(animation/update-bar-values-and-reset-animations
|
||||
{:new-network-values networks
|
||||
:network-bars network-bars
|
||||
:amount->width amount->width
|
||||
:reset-values-fn reset-state-values
|
||||
:lock-press-fn lock-press
|
||||
:unlock-press-fn unlock-press
|
||||
:add-new-timeout add-new-timeout}))
|
||||
[requesting-data?])
|
||||
[:<>
|
||||
(doall
|
||||
(for [[bar-idx bar] (map-indexed vector network-bars)
|
||||
:let [bar-max-width (amount->width (:max-amount bar))
|
||||
bar-width (-> (:amount-shared-value bar)
|
||||
(reanimated/get-shared-value)
|
||||
(amount->width))
|
||||
hide-division? (or (= last-bar-idx bar-idx) @selected-network-idx)
|
||||
this-bar-selected? (= @selected-network-idx bar-idx)]]
|
||||
^{:key (str "network-bar-" bar-idx)}
|
||||
[:f> f-network-bar
|
||||
{:bar bar
|
||||
:max-width bar-max-width
|
||||
:total-width total-width
|
||||
:total-amount total-amount
|
||||
:bar-division? hide-division?
|
||||
:on-top? this-bar-selected?
|
||||
:allow-press? (and (or (not @selected-network-idx) this-bar-selected?)
|
||||
(not requesting-data?)
|
||||
(not @press-locked?))
|
||||
:on-press (fn []
|
||||
(when-not @selected-network-idx
|
||||
(let [[previous-bars [_ & next-bars]] (split-at bar-idx network-bars)
|
||||
number-previous-bars bar-idx]
|
||||
(animation/move-previous-bars
|
||||
{:bars previous-bars
|
||||
:bars-widths-negative bars-widths-negative})
|
||||
(animation/move-pressed-bar
|
||||
{:bar bar
|
||||
:bars-widths-negative bars-widths-negative
|
||||
:number-previous-bars number-previous-bars})
|
||||
(animation/move-next-bars
|
||||
{:bars next-bars
|
||||
:bars-widths-negative bars-widths-negative
|
||||
:number-previous-bars (inc number-previous-bars)
|
||||
:extra-offset (max 0 (- bar-max-width bar-width))
|
||||
:add-new-timeout add-new-timeout}))
|
||||
(animation/show-max-limit-bar bar-opacity-shared-value)
|
||||
(reset! selected-network-idx bar-idx)))
|
||||
:on-new-amount (fn [new-amount]
|
||||
(animation/hide-max-limit-bar bar-opacity-shared-value)
|
||||
(when on-amount-selected
|
||||
(on-amount-selected new-amount @selected-network-idx)))}]))
|
||||
(defn- network-routing-bars
|
||||
[{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
|
||||
(let [[selected-network-idx
|
||||
set-selected-network-idx] (rn/use-state nil)
|
||||
[press-locked?
|
||||
set-press-locked] (rn/use-state false)
|
||||
lock-press (rn/use-callback #(set-press-locked true))
|
||||
unlock-press (rn/use-callback #(set-press-locked false))
|
||||
reset-state-values (rn/use-callback #(set-selected-network-idx nil))
|
||||
bar-opacity-shared-value (reanimated/use-shared-value 0)
|
||||
network-bars (map add-bar-shared-values networks)
|
||||
amount->width #(* % (/ total-width total-amount))
|
||||
bars-widths-negative (map #(-> % get-negative-amount amount->width)
|
||||
network-bars)
|
||||
last-bar-idx (dec (count network-bars))
|
||||
network-bar-on-press (rn/use-callback
|
||||
(fn [bar bar-idx bar-width bar-max-width]
|
||||
(when-not selected-network-idx
|
||||
(let [[previous-bars
|
||||
[_ & next-bars]] (split-at bar-idx network-bars)]
|
||||
(animation/move-previous-bars
|
||||
{:bars previous-bars
|
||||
:bars-widths-negative bars-widths-negative})
|
||||
(animation/move-pressed-bar
|
||||
{:bar bar
|
||||
:bars-widths-negative bars-widths-negative
|
||||
:number-previous-bars bar-idx})
|
||||
(animation/move-next-bars
|
||||
{:bars next-bars
|
||||
:bars-widths-negative bars-widths-negative
|
||||
:number-previous-bars (inc bar-idx)
|
||||
:extra-offset (max 0 (- bar-max-width bar-width))
|
||||
:add-new-timeout add-new-timeout}))
|
||||
(animation/show-max-limit-bar bar-opacity-shared-value)
|
||||
(set-selected-network-idx bar-idx)))
|
||||
[selected-network-idx bars-widths-negative network-bars])
|
||||
on-new-amount (rn/use-callback
|
||||
(fn [new-amount]
|
||||
(animation/hide-max-limit-bar bar-opacity-shared-value)
|
||||
(when on-amount-selected
|
||||
(on-amount-selected new-amount selected-network-idx)))
|
||||
[on-amount-selected selected-network-idx])]
|
||||
(rn/use-effect
|
||||
#(when (and (not requesting-data?) selected-network-idx)
|
||||
(let [bar (nth network-bars selected-network-idx)]
|
||||
(animation/hide-pressed-bar bar amount->width))
|
||||
(animation/update-bar-values-and-reset-animations
|
||||
{:new-network-values networks
|
||||
:network-bars network-bars
|
||||
:amount->width amount->width
|
||||
:reset-values-fn reset-state-values
|
||||
:lock-press-fn lock-press
|
||||
:unlock-press-fn unlock-press
|
||||
:add-new-timeout add-new-timeout}))
|
||||
[requesting-data?])
|
||||
[:<>
|
||||
(doall
|
||||
(for [[bar-idx bar] (map-indexed vector network-bars)
|
||||
:let [bar-max-width (amount->width (:max-amount bar))
|
||||
bar-width (-> (:amount-shared-value bar)
|
||||
(reanimated/get-shared-value)
|
||||
(amount->width))
|
||||
hide-division? (or (= last-bar-idx bar-idx) selected-network-idx)
|
||||
this-bar-selected? (= selected-network-idx bar-idx)]]
|
||||
^{:key (str "network-bar-" bar-idx)}
|
||||
[network-bar
|
||||
{:bar bar
|
||||
:bar-idx bar-idx
|
||||
:bar-width bar-width
|
||||
:bar-max-width bar-max-width
|
||||
:total-width total-width
|
||||
:total-amount total-amount
|
||||
:bar-division? hide-division?
|
||||
:on-top? this-bar-selected?
|
||||
:allow-press? (and (or (not selected-network-idx) this-bar-selected?)
|
||||
(not requesting-data?)
|
||||
(not press-locked?))
|
||||
:on-press network-bar-on-press
|
||||
:on-new-amount on-new-amount}]))
|
||||
|
||||
(let [{:keys [max-amount network-name]} (some->> @selected-network-idx
|
||||
(nth network-bars))
|
||||
limit-bar-width (amount->width max-amount)]
|
||||
[reanimated/view
|
||||
{:style (style/max-limit-bar
|
||||
{:opacity-shared-value bar-opacity-shared-value
|
||||
:width limit-bar-width})}
|
||||
[rn/view {:style (style/max-limit-bar-background network-name)}]
|
||||
[dashed-line network-name]])]))))
|
||||
(let [{:keys [max-amount network-name]} (some->> selected-network-idx
|
||||
(nth network-bars))
|
||||
limit-bar-width (amount->width max-amount)]
|
||||
[reanimated/view
|
||||
{:style (style/max-limit-bar
|
||||
{:opacity-shared-value bar-opacity-shared-value
|
||||
:width limit-bar-width})}
|
||||
[rn/view {:style (style/max-limit-bar-background network-name)}]
|
||||
[dashed-line network-name]])]))
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [networks container-style theme] :as params}]
|
||||
(reagent/with-let [total-width (reagent/atom nil)]
|
||||
[{:keys [networks container-style] :as params}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[total-width
|
||||
set-total-width] (rn/use-state nil)
|
||||
on-layout (rn/use-callback #(let [width (oops/oget % "nativeEvent.layout.width")]
|
||||
(when (not= width total-width)
|
||||
(set-total-width width))))]
|
||||
(rn/use-unmount (fn []
|
||||
(doseq [[_ living-timeout] @timeouts]
|
||||
(js/clearTimeout living-timeout))))
|
||||
[rn/view
|
||||
{:accessibility-label :network-routing
|
||||
:style (style/container container-style theme)
|
||||
:on-layout #(reset! total-width (oops/oget % "nativeEvent.layout.width"))}
|
||||
(when @total-width
|
||||
:on-layout on-layout}
|
||||
(when total-width
|
||||
^{:key (str "network-routing-" (count networks))}
|
||||
[:f> f-network-routing-bars (assoc params :total-width @total-width)])]
|
||||
(finally
|
||||
(doseq [[_ living-timeout] @timeouts]
|
||||
(js/clearTimeout living-timeout)))))
|
||||
[network-routing-bars (assoc params :total-width total-width)])]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal network-routing-schema/?schema)))
|
||||
(def view (schema/instrument #'view-internal network-routing-schema/?schema))
|
||||
|
||||
@@ -14,6 +14,5 @@
|
||||
[:networks {:optional true}
|
||||
[:maybe [:sequential [:map [:source [:maybe :schema.common/image-source]]]]]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:value {:optional true} [:maybe :string]]
|
||||
[:theme :schema.common/theme]]]]
|
||||
[:value {:optional true} [:maybe :string]]]]]
|
||||
:any])
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[quo.foundations.common :as common]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn fiat-format
|
||||
@@ -75,7 +74,8 @@
|
||||
[token-name-text theme text]])
|
||||
|
||||
(defn input-section
|
||||
[{:keys [on-change-text value value-atom on-selection-change on-token-press]}]
|
||||
[{:keys [on-change-text value value-internal set-value-internal on-selection-change
|
||||
on-token-press]}]
|
||||
(let [input-ref (atom nil)
|
||||
set-ref #(reset! input-ref %)
|
||||
focus-input #(when-let [ref ^js @input-ref]
|
||||
@@ -83,7 +83,7 @@
|
||||
controlled-input? (some? value)
|
||||
handle-on-change-text (fn [v]
|
||||
(when-not controlled-input?
|
||||
(reset! value-atom v))
|
||||
(set-value-internal v))
|
||||
(when on-change-text
|
||||
(on-change-text v)))
|
||||
handle-selection-change (fn [^js e]
|
||||
@@ -117,41 +117,43 @@
|
||||
:on-selection-change handle-selection-change
|
||||
:selection (clj->js selection)}
|
||||
controlled-input? (assoc :value value)
|
||||
(not controlled-input?) (assoc :default-value @value-atom))]]
|
||||
(not controlled-input?) (assoc :default-value value-internal))]]
|
||||
[token-label
|
||||
{:theme theme
|
||||
:text (if crypto? token currency)
|
||||
:value (if controlled-input? value @value-atom)}]])))
|
||||
:value (if controlled-input? value value-internal)}]])))
|
||||
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [width (:width (rn/get-window))
|
||||
value-atom (reagent/atom nil)
|
||||
crypto? (reagent/atom true)]
|
||||
(fn [{:keys [theme container-style value on-swap] :as props}]
|
||||
(let [handle-on-swap (fn []
|
||||
(swap! crypto? not)
|
||||
(when on-swap (on-swap @crypto?)))]
|
||||
[rn/view {:style (merge (style/main-container width) container-style)}
|
||||
[rn/view {:style style/amount-container}
|
||||
[input-section
|
||||
(assoc props
|
||||
:value-atom value-atom
|
||||
:crypto? @crypto?)]
|
||||
[button/button
|
||||
{:icon true
|
||||
:icon-only? true
|
||||
:size 32
|
||||
:on-press handle-on-swap
|
||||
:type :outline
|
||||
:accessibility-label :reorder}
|
||||
:i/reorder]]
|
||||
[divider-line/view {:container-style (style/divider theme)}]
|
||||
[data-info
|
||||
(assoc props
|
||||
:crypto? @crypto?
|
||||
:amount (or value @value-atom))]]))))
|
||||
[{:keys [container-style value on-swap] :as props}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
width (:width (rn/get-window))
|
||||
[value-internal set-value-internal] (rn/use-state nil)
|
||||
[crypto? set-crypto] (rn/use-state true)
|
||||
handle-on-swap (rn/use-callback
|
||||
(fn []
|
||||
(set-crypto (not crypto?))
|
||||
(when on-swap (on-swap (not crypto?))))
|
||||
[crypto? on-swap])]
|
||||
[rn/view {:style (merge (style/main-container width) container-style)}
|
||||
[rn/view {:style style/amount-container}
|
||||
[input-section
|
||||
(assoc props
|
||||
:value-internal value-internal
|
||||
:set-value-internal set-value-internal
|
||||
:crypto? crypto?)]
|
||||
[button/button
|
||||
{:icon true
|
||||
:icon-only? true
|
||||
:size 32
|
||||
:on-press handle-on-swap
|
||||
:type :outline
|
||||
:accessibility-label :reorder}
|
||||
:i/reorder]]
|
||||
[divider-line/view {:container-style (style/divider theme)}]
|
||||
[data-info
|
||||
(assoc props
|
||||
:theme theme
|
||||
:crypto? crypto?
|
||||
:amount (or value value-internal))]]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal component-schema/?schema)))
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[:blur? {:optional true} [:maybe :boolean]]
|
||||
[:on-press {:optional true} [:maybe fn?]]
|
||||
[:state {:optional true} [:maybe [:= :disabled]]]
|
||||
[:theme :schema.common/theme]
|
||||
[:second-tag-prefix {:optional true} [:maybe :keyword]]
|
||||
[:third-tag-prefix {:optional true} [:maybe :keyword]]
|
||||
[:fourth-tag-prefix {:optional true} [:maybe :keyword]]
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.hole-view :as hole-view]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
@@ -100,39 +99,37 @@
|
||||
[context-tag/view (merge props {:size 24 :blur? blur?})]])
|
||||
|
||||
(defn- view-internal
|
||||
[_]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn
|
||||
[{:keys [state theme blur?
|
||||
on-press
|
||||
first-tag second-tag third-tag fourth-tag
|
||||
second-tag-prefix third-tag-prefix fourth-tag-prefix]
|
||||
:as props}]
|
||||
[rn/pressable
|
||||
{:style (style/wallet-activity-container {:pressed? @pressed?
|
||||
:theme theme
|
||||
:blur? blur?})
|
||||
:accessibility-label :wallet-activity
|
||||
:disabled (= state :disabled)
|
||||
:on-press on-press
|
||||
:on-press-in (fn [] (reset! pressed? true))
|
||||
:on-press-out (fn [] (reset! pressed? false))}
|
||||
[rn/view
|
||||
{:style {:flex-direction :row}}
|
||||
[transaction-icon-view props]
|
||||
[rn/view
|
||||
{:style style/content-container}
|
||||
[transaction-header props]
|
||||
[rn/view {:style style/content-line}
|
||||
(when first-tag [prop-tag first-tag blur?])
|
||||
(when second-tag-prefix [prop-text second-tag-prefix theme])
|
||||
(when second-tag [prop-tag second-tag blur?])]
|
||||
[rn/view {:style style/content-line}
|
||||
(when third-tag-prefix [prop-text third-tag-prefix theme])
|
||||
(when third-tag [prop-tag third-tag blur?])
|
||||
(when fourth-tag-prefix [prop-text fourth-tag-prefix theme])
|
||||
(when fourth-tag [prop-tag fourth-tag blur?])]]]])))
|
||||
[{:keys [state blur? first-tag second-tag third-tag fourth-tag on-press
|
||||
second-tag-prefix third-tag-prefix fourth-tag-prefix]
|
||||
:as props}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed?
|
||||
set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))]
|
||||
[rn/pressable
|
||||
{:style (style/wallet-activity-container {:pressed? pressed?
|
||||
:theme theme
|
||||
:blur? blur?})
|
||||
:accessibility-label :wallet-activity
|
||||
:disabled (= state :disabled)
|
||||
:on-press on-press
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out}
|
||||
[rn/view
|
||||
{:style {:flex-direction :row}}
|
||||
[transaction-icon-view props]
|
||||
[rn/view
|
||||
{:style style/content-container}
|
||||
[transaction-header props]
|
||||
[rn/view {:style style/content-line}
|
||||
(when first-tag [prop-tag first-tag blur?])
|
||||
(when second-tag-prefix [prop-text second-tag-prefix theme])
|
||||
(when second-tag [prop-tag second-tag blur?])]
|
||||
[rn/view {:style style/content-line}
|
||||
(when third-tag-prefix [prop-text third-tag-prefix theme])
|
||||
(when third-tag [prop-tag third-tag blur?])
|
||||
(when fourth-tag-prefix [prop-text fourth-tag-prefix theme])
|
||||
(when fourth-tag [prop-tag fourth-tag blur?])]]]]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal component-schema/?schema)))
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
(def ui-themed
|
||||
{:no-funds
|
||||
{:light (js/require "../resources/images/ui2/no-funds-light.png")
|
||||
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}})
|
||||
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}
|
||||
:no-contacts-to-chat
|
||||
{:light (js/require "../resources/images/ui2/no-contacts-to-chat-light.png")
|
||||
:dark (js/require "../resources/images/ui2/no-contacts-to-chat-dark.png")}})
|
||||
|
||||
(defn get-themed-image
|
||||
[k theme]
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
(defn contacts-section-header
|
||||
[{:keys [title]}]
|
||||
(let [theme (quo.theme/use-theme-value)]
|
||||
[quo/divider-label {:container-style {:background-color (style/contacts-section-header theme)}}
|
||||
[quo/divider-label {:container-style (style/contacts-section-header theme)}
|
||||
title]))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn contact-list-item
|
||||
[{:keys [on-press on-long-press accessory allow-multiple-presses?]}
|
||||
[{:keys [on-press on-long-press accessory allow-multiple-presses? disabled?]}
|
||||
{:keys [primary-name secondary-name public-key compressed-key ens-verified added?]}
|
||||
theme]
|
||||
(let [photo-path (rf/sub [:chats/photo-path public-key])
|
||||
@@ -24,4 +24,5 @@
|
||||
:contact? added?
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:accessory accessory}]))
|
||||
:accessory accessory
|
||||
:disabled? disabled?}]))
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
{:keys [primary-name public-key]} (when one-contact-selected?
|
||||
(rf/sub [:contacts/contact-by-identity
|
||||
(first selected-contacts)]))]
|
||||
(rn/use-unmount #(rf/dispatch [:group-chat/clear-contacts]))
|
||||
[rn/view {:flex 1}
|
||||
[rn/view {:padding-horizontal 20}
|
||||
[quo/button
|
||||
|
||||
@@ -41,8 +41,13 @@
|
||||
|
||||
(defn f-send-button
|
||||
[props state animations window-height images? btn-opacity z-index edit]
|
||||
(let [{:keys [text-value]} state
|
||||
customization-color (rf/sub [:profile/customization-color])]
|
||||
(let [{:keys [text-value]} state
|
||||
profile-customization-color (rf/sub [:profile/customization-color])
|
||||
{:keys [chat-id chat-type]
|
||||
chat-color :color} (rf/sub [:chats/current-chat-chat-view])
|
||||
contact-customization-color (when (= chat-type constants/one-to-one-chat-type)
|
||||
(rf/sub [:contacts/contact-customization-color-by-address
|
||||
chat-id]))]
|
||||
(rn/use-effect (fn []
|
||||
;; Handle send button opacity animation and z-index when input content changes
|
||||
(if (or (seq @text-value) images?)
|
||||
@@ -60,7 +65,7 @@
|
||||
[quo/button
|
||||
{:icon-only? true
|
||||
:size 32
|
||||
:customization-color customization-color
|
||||
:customization-color (or contact-customization-color chat-color profile-customization-color)
|
||||
:accessibility-label :send-message-button
|
||||
:on-press #(send-message props state animations window-height edit)}
|
||||
:i/arrow-up]]))
|
||||
|
||||
@@ -29,11 +29,15 @@
|
||||
|
||||
(defn pinned-message
|
||||
[{:keys [from quoted-message timestamp-str]}]
|
||||
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
|
||||
customization-color (rf/sub [:profile/customization-color])]
|
||||
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
|
||||
one-to-one-chat? (rf/sub [:current-chat/one-to-one-chat?])
|
||||
current-chat-color (rf/sub [:chats/current-chat-color])
|
||||
contact-customization-color (rf/sub [:contacts/contact-customization-color-by-address from])]
|
||||
[quo/system-message
|
||||
{:type :pinned
|
||||
:pinned-by primary-name
|
||||
:customization-color customization-color
|
||||
:customization-color (if one-to-one-chat?
|
||||
contact-customization-color
|
||||
current-chat-color)
|
||||
:child [reply/quoted-message quoted-message false true]
|
||||
:timestamp timestamp-str}]))
|
||||
|
||||
@@ -79,16 +79,17 @@
|
||||
|
||||
(defn system-message-contact-request
|
||||
[{:keys [chat-id timestamp-str from]} type]
|
||||
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity chat-id])
|
||||
contact (rf/sub [:contacts/contact-by-address chat-id])
|
||||
photo-path (when (seq (:images contact)) (rf/sub [:chats/photo-path chat-id]))
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
public-key (rf/sub [:profile/public-key])]
|
||||
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity chat-id])
|
||||
{:keys [images]
|
||||
contact-customization-color
|
||||
:customization-color} (rf/sub [:contacts/contact-by-address chat-id])
|
||||
photo-path (when (seq images) (rf/sub [:chats/photo-path chat-id]))
|
||||
public-key (rf/sub [:profile/public-key])]
|
||||
[quo/system-message
|
||||
{:type type
|
||||
:timestamp timestamp-str
|
||||
:display-name primary-name
|
||||
:customization-color customization-color
|
||||
:customization-color contact-customization-color
|
||||
:photo-path photo-path
|
||||
:incoming? (not= public-key from)}]))
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
(ns status-im.contexts.chat.messenger.messages.list.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.foundations.shadows :as shadows]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
|
||||
|
||||
@@ -23,10 +25,12 @@
|
||||
[bottom theme top-margin]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:bottom bottom}
|
||||
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||
:padding-horizontal 20
|
||||
:border-radius 20
|
||||
:margin-top top-margin}))
|
||||
(merge
|
||||
(shadows/get 2 (quo.theme/get-theme) :inverted)
|
||||
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||
:padding-horizontal 20
|
||||
:border-radius 20
|
||||
:margin-top top-margin})))
|
||||
|
||||
(defn header-image
|
||||
[scale top left theme]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns status-im.contexts.chat.messenger.messages.list.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[legacy.status-im.ui.screens.chat.group :as chat.group]
|
||||
[oops.core :as oops]
|
||||
[quo.core :as quo]
|
||||
@@ -112,19 +113,24 @@
|
||||
[rn/view {:style {:height height}}]))
|
||||
|
||||
(defn list-footer-avatar
|
||||
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color]}]
|
||||
(let [scale (reanimated/interpolate distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[1 0.4]
|
||||
messages.constants/default-extrapolation-option)
|
||||
top (reanimated/interpolate distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[-44 -12]
|
||||
messages.constants/default-extrapolation-option)
|
||||
left (reanimated/interpolate distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[16 -8]
|
||||
messages.constants/default-extrapolation-option)]
|
||||
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color
|
||||
emoji chat-type chat-name last-message]}]
|
||||
(let [scale (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
|
||||
[1 0.4]
|
||||
messages.constants/default-extrapolation-option)
|
||||
top (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
|
||||
[-44 -12]
|
||||
messages.constants/default-extrapolation-option)
|
||||
left (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
|
||||
[16 -8]
|
||||
messages.constants/default-extrapolation-option)
|
||||
community-channel? (= chat-type constants/community-chat-type)]
|
||||
[reanimated/view
|
||||
{:style (style/header-image scale top left theme)}
|
||||
(if group-chat
|
||||
@@ -132,7 +138,10 @@
|
||||
{:customization-color color
|
||||
:size :size-80
|
||||
:picture profile-picture
|
||||
:override-theme :dark}]
|
||||
:emoji (when (and (not (string/blank? emoji))
|
||||
community-channel?)
|
||||
(string/trim emoji))
|
||||
:chat-name chat-name}]
|
||||
[quo/user-avatar
|
||||
{:full-name display-name
|
||||
:online? online?
|
||||
@@ -140,15 +149,17 @@
|
||||
:size :big}])]))
|
||||
|
||||
(defn chat-display-name
|
||||
[{:keys [distance-from-list-top display-name contact theme]}]
|
||||
(let [top (reanimated/interpolate distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[0 -35]
|
||||
messages.constants/default-extrapolation-option)
|
||||
left (reanimated/interpolate distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[0 40]
|
||||
messages.constants/default-extrapolation-option)]
|
||||
[{:keys [distance-from-list-top display-name contact theme last-message]}]
|
||||
(let [top (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
|
||||
[0 -35]
|
||||
messages.constants/default-extrapolation-option)
|
||||
left (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
|
||||
[0 40]
|
||||
messages.constants/default-extrapolation-option)]
|
||||
[reanimated/view
|
||||
{:style (style/user-name-container top left)}
|
||||
[rn/view
|
||||
@@ -195,54 +206,55 @@
|
||||
muted?)))}]}]))
|
||||
|
||||
(defn bio-and-actions
|
||||
[{:keys [distance-from-list-top bio chat-id customization-color]}]
|
||||
(let [has-bio (seq bio)
|
||||
[{:keys [distance-from-list-top bio chat-id customization-color last-message description]}]
|
||||
(let [has-bio (seq (or bio description))
|
||||
top (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
|
||||
[(if has-bio 8 16) (if has-bio -28 -20)]
|
||||
messages.constants/default-extrapolation-option)]
|
||||
[reanimated/view
|
||||
{:style (style/bio-and-actions top)}
|
||||
(when has-bio
|
||||
[quo/text bio])
|
||||
[quo/text (or bio description)])
|
||||
[actions chat-id customization-color]]))
|
||||
|
||||
(defn footer-component
|
||||
[{:keys [chat distance-from-list-top theme customization-color]}]
|
||||
(let [{:keys [chat-id chat-name emoji chat-type
|
||||
group-chat color]} chat
|
||||
display-name (cond
|
||||
(= chat-type constants/one-to-one-chat-type)
|
||||
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
|
||||
(= chat-type constants/community-chat-type)
|
||||
(str (when emoji (str emoji " ")) "# " chat-name)
|
||||
:else (str emoji chat-name))
|
||||
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
|
||||
online? (rf/sub [:visibility-status-updates/online? chat-id])
|
||||
contact (when-not group-chat
|
||||
(rf/sub [:contacts/contact-by-address chat-id]))
|
||||
photo-path (rf/sub [:chats/photo-path chat-id])
|
||||
top-margin (+ (safe-area/get-top)
|
||||
messages.constants/top-bar-height
|
||||
messages.constants/header-container-top-margin
|
||||
32)
|
||||
background-color (colors/theme-colors
|
||||
(colors/resolve-color customization-color theme 20)
|
||||
(colors/resolve-color customization-color theme 40)
|
||||
theme)
|
||||
bottom (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[32 -4]
|
||||
messages.constants/default-extrapolation-option)
|
||||
background-opacity (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[messages.constants/header-container-top-margin
|
||||
(+ messages.constants/header-animation-distance
|
||||
messages.constants/header-container-top-margin)]
|
||||
[1 0]
|
||||
messages.constants/default-extrapolation-option)]
|
||||
group-chat color description
|
||||
last-message]} chat
|
||||
display-name (cond
|
||||
(= chat-type constants/one-to-one-chat-type)
|
||||
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
|
||||
(= chat-type constants/community-chat-type)
|
||||
(str "# " chat-name)
|
||||
:else (str emoji chat-name))
|
||||
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
|
||||
online? (rf/sub [:visibility-status-updates/online? chat-id])
|
||||
contact (when-not group-chat
|
||||
(rf/sub [:contacts/contact-by-address chat-id]))
|
||||
photo-path (rf/sub [:chats/photo-path chat-id])
|
||||
top-margin (+ (safe-area/get-top)
|
||||
messages.constants/top-bar-height
|
||||
messages.constants/header-container-top-margin
|
||||
32)
|
||||
background-color (colors/theme-colors
|
||||
(colors/resolve-color customization-color theme 20)
|
||||
(colors/resolve-color customization-color theme 40)
|
||||
theme)
|
||||
bottom (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[0 messages.constants/header-container-top-margin]
|
||||
[32 -4]
|
||||
messages.constants/default-extrapolation-option)
|
||||
background-opacity (reanimated/interpolate
|
||||
distance-from-list-top
|
||||
[messages.constants/header-container-top-margin
|
||||
(+ messages.constants/header-animation-distance
|
||||
messages.constants/header-container-top-margin)]
|
||||
[1 0]
|
||||
messages.constants/default-extrapolation-option)]
|
||||
[:<>
|
||||
[reanimated/view
|
||||
{:style (style/background-container background-color background-opacity top-margin)}]
|
||||
@@ -254,18 +266,25 @@
|
||||
:theme theme
|
||||
:profile-picture photo-path
|
||||
:group-chat group-chat
|
||||
:color color}]
|
||||
:color color
|
||||
:emoji emoji
|
||||
:chat-type chat-type
|
||||
:chat-name chat-name
|
||||
:last-message last-message}]
|
||||
[chat-display-name
|
||||
{:distance-from-list-top distance-from-list-top
|
||||
:display-name display-name
|
||||
:theme theme
|
||||
:contact contact
|
||||
:group-chat group-chat}]
|
||||
:group-chat group-chat
|
||||
:last-message last-message}]
|
||||
[bio-and-actions
|
||||
{:distance-from-list-top distance-from-list-top
|
||||
:bio bio
|
||||
:chat-id chat-id
|
||||
:customization-color customization-color}]]]))
|
||||
:customization-color customization-color
|
||||
:description description
|
||||
:last-message last-message}]]]))
|
||||
|
||||
(defn list-footer
|
||||
[props]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns status-im.contexts.chat.messenger.messages.navigation.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[re-frame.db]
|
||||
@@ -28,7 +29,7 @@
|
||||
[:contacts/contact-two-names-by-identity
|
||||
chat-id]))
|
||||
(= chat-type constants/community-chat-type)
|
||||
(str (when emoji (str emoji " ")) "# " chat-name)
|
||||
(str "# " chat-name)
|
||||
:else (str emoji chat-name))
|
||||
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
|
||||
photo-path (when-not group-chat (rf/sub [:chats/photo-path chat-id]))
|
||||
@@ -53,7 +54,10 @@
|
||||
{:customization-color color
|
||||
:size :size-32
|
||||
:picture photo-path
|
||||
:override-theme :dark}]
|
||||
:override-theme :dark
|
||||
:emoji (when-not (string/blank? emoji)
|
||||
(string/trim emoji))
|
||||
:chat-name chat-name}]
|
||||
[quo/user-avatar
|
||||
{:full-name display-name
|
||||
:online? online?
|
||||
@@ -111,27 +115,29 @@
|
||||
|
||||
(defn view
|
||||
[{:keys [distance-from-list-top chat-screen-layout-calculations-complete?]}]
|
||||
(let [{:keys [chat-id chat-type] :as chat} (rf/sub [:chats/current-chat-chat-view])
|
||||
all-loaded? (reanimated/use-shared-value false)
|
||||
all-loaded-sub (rf/sub [:chats/all-loaded? chat-id])
|
||||
top-insets (safe-area/get-top)
|
||||
top-bar-height messages.constants/top-bar-height
|
||||
navigation-view-height (+ top-bar-height top-insets)
|
||||
navigation-buttons-opacity (worklets/navigation-buttons-complete-opacity
|
||||
chat-screen-layout-calculations-complete?)
|
||||
reached-threshold? (messages.worklets/use-messages-scrolled-to-threshold
|
||||
distance-from-list-top
|
||||
top-bar-height)
|
||||
button-background (if reached-threshold? :photo :blur)]
|
||||
(let [{:keys [chat-id chat-type last-message]
|
||||
:as chat} (rf/sub [:chats/current-chat-chat-view])
|
||||
all-loaded? (reanimated/use-shared-value false)
|
||||
all-loaded-sub (rf/sub [:chats/all-loaded? chat-id])
|
||||
top-insets (safe-area/get-top)
|
||||
top-bar-height messages.constants/top-bar-height
|
||||
navigation-view-height (+ top-bar-height top-insets)
|
||||
navigation-buttons-opacity (worklets/navigation-buttons-complete-opacity
|
||||
chat-screen-layout-calculations-complete?)
|
||||
reached-threshold? (messages.worklets/use-messages-scrolled-to-threshold
|
||||
distance-from-list-top
|
||||
top-bar-height)
|
||||
button-background (if reached-threshold? :photo :blur)]
|
||||
(rn/use-effect (fn [] (reanimated/set-shared-value all-loaded? all-loaded-sub))
|
||||
[all-loaded-sub])
|
||||
[rn/view
|
||||
{:style (style/navigation-view navigation-view-height messages.constants/pinned-banner-height)}
|
||||
[animated-background-and-pinned-banner
|
||||
{:chat-id chat-id
|
||||
:navigation-view-height navigation-view-height
|
||||
:distance-from-list-top distance-from-list-top
|
||||
:all-loaded? all-loaded?}]
|
||||
(when (seq last-message)
|
||||
[animated-background-and-pinned-banner
|
||||
{:chat-id chat-id
|
||||
:navigation-view-height navigation-view-height
|
||||
:distance-from-list-top distance-from-list-top
|
||||
:all-loaded? all-loaded?}])
|
||||
[rn/view {:style (style/header-container top-insets top-bar-height)}
|
||||
[reanimated/view {:style (style/button-animation-container navigation-buttons-opacity)}
|
||||
[quo/button
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
|
||||
(def divider
|
||||
{:padding-horizontal 20
|
||||
:margin-top 16
|
||||
:margin-top 12
|
||||
:margin-bottom 8})
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
[{:keys [title]}]
|
||||
(when-not (= title no-title)
|
||||
[quo/divider-label
|
||||
{:container-style style/divider}
|
||||
{:container-style style/divider
|
||||
:tight? false}
|
||||
title]))
|
||||
|
||||
(defn key-fn
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
[bottom-inset]
|
||||
{:left 0
|
||||
:right 0
|
||||
:height (+ bottom-inset (if platform/ios? 65 85))
|
||||
:height (+ bottom-inset (if platform/ios? 51 85))
|
||||
:position :absolute
|
||||
:bottom 0})
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
(ns status-im.contexts.communities.actions.invite-contacts.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.safe-area :as safe-area]))
|
||||
|
||||
(def contact-selection-heading
|
||||
{:flex-direction :row
|
||||
:justify-content :space-between
|
||||
:align-items :flex-end
|
||||
:margin-top 24
|
||||
:margin-bottom 16})
|
||||
|
||||
(def chat-button
|
||||
{:position :absolute
|
||||
:bottom (safe-area/get-bottom)
|
||||
:left 20
|
||||
:right 20})
|
||||
|
||||
(defn no-contacts
|
||||
[]
|
||||
{:margin-bottom (+ 96 (safe-area/get-bottom))
|
||||
:flex 1
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def context-tag
|
||||
{:align-self :flex-start
|
||||
:margin-top -8
|
||||
:margin-bottom 12})
|
||||
|
||||
(def no-contacts-text
|
||||
{:margin-bottom 2
|
||||
:margin-top 12})
|
||||
|
||||
(def no-contacts-button-container
|
||||
{:margin-top 20
|
||||
:margin-bottom 12})
|
||||
|
||||
(defn section-list-container-style
|
||||
[theme]
|
||||
{:padding-bottom 70
|
||||
:background-color (colors/theme-colors colors/white
|
||||
colors/neutral-95
|
||||
theme)})
|
||||
|
||||
(defn invite-to-community-text
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white theme)})
|
||||
@@ -0,0 +1,141 @@
|
||||
(ns status-im.contexts.communities.actions.invite-contacts.view
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.resources :as resources]
|
||||
[quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.gesture :as gesture]
|
||||
[react-native.share :as share]
|
||||
[status-im.common.contact-list-item.view :as contact-list-item]
|
||||
[status-im.common.contact-list.view :as contact-list]
|
||||
[status-im.contexts.communities.actions.invite-contacts.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- no-contacts-view
|
||||
[{:keys [theme id]}]
|
||||
(let [customization-color (rf/sub [:profile/customization-color])
|
||||
{:keys [universal-profile-url]} (rf/sub [:profile/profile])
|
||||
on-press-share-community (rn/use-callback
|
||||
#(rf/dispatch [:communities/share-community-url-with-data
|
||||
id]))
|
||||
on-press-share-profile (rn/use-callback #(share/open {:url universal-profile-url})
|
||||
[universal-profile-url])]
|
||||
[rn/view
|
||||
{:style (style/no-contacts)}
|
||||
[rn/image {:source (resources/get-themed-image :no-contacts-to-chat theme)}]
|
||||
[quo/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:style style/no-contacts-text}
|
||||
(i18n/label :t/you-have-no-contacts)]
|
||||
[quo/text
|
||||
{:weight :regular
|
||||
:size :paragraph-2}
|
||||
(i18n/label :t/dont-yell-at-me)]
|
||||
[quo/button
|
||||
{:customization-color customization-color
|
||||
:theme theme
|
||||
:type :primary
|
||||
:size 32
|
||||
:container-style style/no-contacts-button-container
|
||||
:on-press on-press-share-community}
|
||||
(i18n/label :t/send-community-link)]
|
||||
[quo/button
|
||||
{:customization-color customization-color
|
||||
:theme theme
|
||||
:type :grey
|
||||
:size 32
|
||||
:on-press on-press-share-profile}
|
||||
(i18n/label :t/invite-friends-to-status)]]))
|
||||
|
||||
(defn- contact-item
|
||||
[{:keys [public-key]
|
||||
:as item}]
|
||||
(let [user-selected? (rf/sub [:is-contact-selected? public-key])
|
||||
{:keys [id]} (rf/sub [:get-screen-params])
|
||||
community-members-keys (set (keys (rf/sub [:communities/community-members id])))
|
||||
community-member? (boolean (community-members-keys public-key))
|
||||
on-toggle (fn []
|
||||
(when-not community-member?
|
||||
(if user-selected?
|
||||
(rf/dispatch [:deselect-contact public-key])
|
||||
(rf/dispatch [:select-contact public-key]))))]
|
||||
[contact-list-item/contact-list-item
|
||||
{:on-press on-toggle
|
||||
:allow-multiple-presses? true
|
||||
:accessory {:type :checkbox
|
||||
:disabled? community-member?
|
||||
:checked? (or community-member? user-selected?)
|
||||
:on-check on-toggle}
|
||||
:disabled? community-member?}
|
||||
item]))
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [theme]}]
|
||||
(fn []
|
||||
(rn/use-unmount #(rf/dispatch [:group-chat/clear-contacts]))
|
||||
(let [customization-color (rf/sub [:profile/customization-color])
|
||||
{:keys [id]} (rf/sub [:get-screen-params])
|
||||
contacts (rf/sub [:contacts/filtered-active-sections])
|
||||
selected (rf/sub [:group/selected-contacts])
|
||||
{:keys [name images]} (rf/sub [:communities/community id])
|
||||
selected-contacts-count (count selected)
|
||||
on-press (fn []
|
||||
(rf/dispatch [:communities/share-community-confirmation-pressed
|
||||
selected id])
|
||||
(rf/dispatch [:navigate-back])
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:type :positive
|
||||
:theme theme
|
||||
:text (if (= 1 selected-contacts-count)
|
||||
(i18n/label :t/one-user-was-invited)
|
||||
(i18n/label
|
||||
:t/n-users-were-invited
|
||||
{:count selected-contacts-count}))}]))
|
||||
{window-height :height} (rn/get-window)]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[rn/view {:style {:padding-horizontal 20}}
|
||||
[quo/button
|
||||
{:type :grey
|
||||
:size 32
|
||||
:icon-only? true
|
||||
:on-press #(rf/dispatch [:navigate-back])}
|
||||
:i/close]
|
||||
[rn/view {:style style/contact-selection-heading}
|
||||
[quo/text
|
||||
{:weight :semi-bold
|
||||
:size :heading-1
|
||||
:style (style/invite-to-community-text theme)}
|
||||
(i18n/label :t/invite-to-community)]]
|
||||
[quo/context-tag
|
||||
{:type :community
|
||||
:size 24
|
||||
:community-logo (:thumbnail images)
|
||||
:community-name name
|
||||
:container-style style/context-tag}]]
|
||||
(if (empty? contacts)
|
||||
[no-contacts-view
|
||||
{:theme theme
|
||||
:id id}]
|
||||
[:<>
|
||||
[gesture/section-list
|
||||
{:key-fn :public-key
|
||||
:sticky-section-headers-enabled true
|
||||
:sections contacts
|
||||
:render-section-header-fn contact-list/contacts-section-header
|
||||
:content-container-style (style/section-list-container-style theme)
|
||||
:render-fn contact-item
|
||||
:style {:height window-height}}]
|
||||
(when (pos? selected-contacts-count)
|
||||
[quo/button
|
||||
{:type :primary
|
||||
:accessibility-label :next-button
|
||||
:customization-color customization-color
|
||||
:container-style style/chat-button
|
||||
:on-press on-press}
|
||||
(if (= 1 selected-contacts-count)
|
||||
(i18n/label :t/invite-1-user)
|
||||
(i18n/label :t/invite-n-users {:count selected-contacts-count}))])])])))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
@@ -1,11 +1,8 @@
|
||||
(ns status-im.contexts.communities.events
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[legacy.status-im.data-store.chats :as data-store.chats]
|
||||
[legacy.status-im.data-store.communities :as data-store.communities]
|
||||
[legacy.status-im.mailserver.core :as mailserver]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.share :as share]
|
||||
[schema.core :as schema]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
|
||||
@@ -14,9 +11,9 @@
|
||||
status-im.contexts.communities.actions.airdrop-addresses.events
|
||||
status-im.contexts.communities.actions.community-options.events
|
||||
status-im.contexts.communities.actions.leave.events
|
||||
[status-im.contexts.communities.utils :as utils]
|
||||
[status-im.navigation.events :as navigation]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn handle-community
|
||||
@@ -152,43 +149,100 @@
|
||||
:on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %])
|
||||
:on-error #(log/error "failed to fetch collapsed community categories" %)}]}))
|
||||
|
||||
(rf/reg-event-fx :communities/get-community-channel-share-data
|
||||
(fn [_ [chat-id on-success]]
|
||||
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_shareCommunityChannelURLWithData"
|
||||
:params [{:CommunityID community-id :ChannelID channel-id}]
|
||||
:on-success on-success
|
||||
:on-error (fn [err]
|
||||
(log/error "failed to retrieve community channel url with data"
|
||||
{:error err
|
||||
:chat-id chat-id
|
||||
:event :communities/get-community-channel-share-data}))}]})))
|
||||
(defn 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/share-community-channel-url-with-data
|
||||
(fn [_ [chat-id]]
|
||||
(let [title (i18n/label :t/channel-on-status)
|
||||
on-success (fn [url]
|
||||
(share/open
|
||||
(if platform/ios?
|
||||
{:activityItemSources [{:placeholderItem {:type "text"
|
||||
:content title}
|
||||
:item {:default {:type "url"
|
||||
:content url}}
|
||||
:linkMetadata {:title title}}]}
|
||||
{:title title
|
||||
:subject title
|
||||
:message url
|
||||
:url url
|
||||
:isNewTask true})))]
|
||||
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
|
||||
(rf/reg-event-fx :communities/initialize-permission-addresses
|
||||
initialize-permission-addresses)
|
||||
|
||||
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
|
||||
(fn [_ [chat-id]]
|
||||
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
|
||||
{:chat-id chat-id
|
||||
:url %}])]
|
||||
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
|
||||
(defn update-previous-permission-addresses
|
||||
[{:keys [db]} [community-id]]
|
||||
(when community-id
|
||||
(let [accounts (utils/sorted-non-watch-only-accounts db)
|
||||
selected-permission-addresses (get-in db
|
||||
[:communities community-id
|
||||
:selected-permission-addresses])
|
||||
selected-accounts (filter #(contains? selected-permission-addresses (:address %))
|
||||
accounts)
|
||||
current-airdrop-address (get-in db [:communities community-id :airdrop-address])
|
||||
share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])]
|
||||
{:db (update-in db
|
||||
[:communities community-id]
|
||||
assoc
|
||||
:previous-share-all-addresses? share-all-addresses?
|
||||
:previous-permission-addresses selected-permission-addresses
|
||||
:airdrop-address (if (contains? selected-permission-addresses
|
||||
current-airdrop-address)
|
||||
current-airdrop-address
|
||||
(:address (first selected-accounts))))})))
|
||||
|
||||
(rf/reg-event-fx :communities/update-previous-permission-addresses
|
||||
update-previous-permission-addresses)
|
||||
|
||||
(defn toggle-selected-permission-address
|
||||
[{:keys [db]} [address community-id]]
|
||||
(let [selected-permission-addresses
|
||||
(get-in db [:communities community-id :selected-permission-addresses])
|
||||
updated-selected-permission-addresses
|
||||
(if (contains? selected-permission-addresses address)
|
||||
(disj selected-permission-addresses address)
|
||||
(conj selected-permission-addresses address))]
|
||||
{:db (assoc-in db
|
||||
[:communities community-id :selected-permission-addresses]
|
||||
updated-selected-permission-addresses)
|
||||
:fx [(when community-id
|
||||
[:dispatch
|
||||
[:communities/check-permissions-to-join-community community-id
|
||||
updated-selected-permission-addresses :based-on-client-selection]])]}))
|
||||
|
||||
(rf/reg-event-fx :communities/toggle-selected-permission-address
|
||||
toggle-selected-permission-address)
|
||||
|
||||
(defn toggle-share-all-addresses
|
||||
[{:keys [db]} [community-id]]
|
||||
(let [share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])
|
||||
next-share-all-addresses? (not share-all-addresses?)
|
||||
accounts (utils/sorted-non-watch-only-accounts db)
|
||||
addresses (set (map :address accounts))]
|
||||
{:db (update-in db
|
||||
[:communities community-id]
|
||||
assoc
|
||||
:share-all-addresses? next-share-all-addresses?
|
||||
:selected-permission-addresses addresses)
|
||||
:fx [(when (and community-id next-share-all-addresses?)
|
||||
[:dispatch
|
||||
[:communities/check-permissions-to-join-community community-id
|
||||
addresses :based-on-client-selection]])]}))
|
||||
|
||||
(rf/reg-event-fx :communities/toggle-share-all-addresses
|
||||
toggle-share-all-addresses)
|
||||
|
||||
(rf/reg-event-fx :communities/reset-selected-permission-addresses
|
||||
(fn [{:keys [db]} [community-id]]
|
||||
(when community-id
|
||||
{:db (update-in db
|
||||
[:communities community-id]
|
||||
assoc
|
||||
:selected-permission-addresses
|
||||
(get-in db [:communities community-id :previous-permission-addresses])
|
||||
:share-all-addresses?
|
||||
(get-in db [:communities community-id :previous-share-all-addresses?]))
|
||||
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
|
||||
|
||||
(rf/reg-event-fx :communities/set-airdrop-address
|
||||
(fn [{:keys [db]} [address community-id]]
|
||||
{:db (assoc-in db [:communities community-id :airdrop-address] address)}))
|
||||
|
||||
(defn community-fetched
|
||||
[{:keys [db]} [community-id community]]
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
(ns status-im.contexts.communities.sharing.events
|
||||
(:require [legacy.status-im.data-store.chats :as data-store.chats]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.share :as share]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/reg-event-fx :communities/invite-people-pressed
|
||||
(fn [{:keys [db]} [id]]
|
||||
{:db (assoc db :communities/community-id-input id)
|
||||
:fx [[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch [:open-modal :invite-people-community {:id id}]]]}))
|
||||
|
||||
(rf/reg-event-fx :communities/share-community-pressed
|
||||
(fn [{:keys [db]} [id]]
|
||||
{:db (assoc db :communities/community-id-input id)
|
||||
:fx [[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch [:open-modal :legacy-invite-people-community {:id id}]]]}))
|
||||
|
||||
(rf/reg-event-fx :communities/share-community-confirmation-pressed
|
||||
(fn [_ [users-public-keys community-id]]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_shareCommunity"
|
||||
:params [{:communityId community-id
|
||||
:users users-public-keys}]
|
||||
:js-response true
|
||||
:on-success [:sanitize-messages-and-process-response]
|
||||
:on-error (fn [err]
|
||||
(log/error {:message "failed to share community"
|
||||
:community-id community-id
|
||||
:err err}))}]]]}))
|
||||
|
||||
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
|
||||
(fn [_ [chat-id]]
|
||||
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
|
||||
{:chat-id chat-id
|
||||
:url %}])]
|
||||
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
|
||||
|
||||
(rf/reg-event-fx :communities/share-community-url-with-data
|
||||
(fn [_ [community-id]]
|
||||
(let [title (i18n/label :t/community-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/reg-event-fx :communities/get-community-channel-share-data
|
||||
(fn [_ [chat-id on-success]]
|
||||
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_shareCommunityChannelURLWithData"
|
||||
:params [{:CommunityID community-id :ChannelID channel-id}]
|
||||
:on-success on-success
|
||||
:on-error (fn [err]
|
||||
(log/error "failed to retrieve community channel url with data"
|
||||
{:error err
|
||||
:chat-id chat-id
|
||||
:event :communities/get-community-channel-share-data}))}]})))
|
||||
|
||||
(rf/reg-event-fx :communities/get-community-share-data
|
||||
(fn [_ [community-id on-success]]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_shareCommunityURLWithData"
|
||||
:params [community-id]
|
||||
:on-success on-success
|
||||
:on-error (fn [err]
|
||||
(log/error "failed to retrieve community url with data"
|
||||
{:error err
|
||||
:community-id community-id
|
||||
:event :communities/get-community-share-data}))}]}))
|
||||
|
||||
(rf/reg-event-fx :communities/share-community-channel-url-with-data
|
||||
(fn [_ [chat-id]]
|
||||
(let [title (i18n/label :t/channel-on-status)
|
||||
on-success (fn [url]
|
||||
(share/open
|
||||
(if platform/ios?
|
||||
{:activityItemSources [{:placeholderItem {:type "text"
|
||||
:content title}
|
||||
:item {:default {:type "url"
|
||||
:content url}}
|
||||
:linkMetadata {:title title}}]}
|
||||
{:title title
|
||||
:subject title
|
||||
:message url
|
||||
:url url
|
||||
:isNewTask true})))]
|
||||
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
|
||||
@@ -22,6 +22,7 @@
|
||||
status-im.contexts.chat.messenger.photo-selector.events
|
||||
status-im.contexts.communities.events
|
||||
status-im.contexts.communities.overview.events
|
||||
status-im.contexts.communities.sharing.events
|
||||
status-im.contexts.onboarding.common.overlay.events
|
||||
status-im.contexts.onboarding.events
|
||||
status-im.contexts.profile.events
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
[status-im.contexts.chat.messenger.messages.view :as chat]
|
||||
[status-im.contexts.chat.messenger.photo-selector.view :as photo-selector]
|
||||
[status-im.contexts.communities.actions.accounts-selection.view :as communities.accounts-selection]
|
||||
[status-im.contexts.communities.actions.addresses-for-permissions.view :as addresses-for-permissions]
|
||||
[status-im.contexts.communities.actions.addresses-for-permissions.view :as
|
||||
addresses-for-permissions]
|
||||
[status-im.contexts.communities.actions.airdrop-addresses.view :as airdrop-addresses]
|
||||
[status-im.contexts.communities.actions.channel-view-details.view :as
|
||||
channel-view-channel-members-and-details]
|
||||
[status-im.contexts.communities.actions.invite-contacts.view :as communities.invite]
|
||||
[status-im.contexts.communities.actions.request-to-join.view :as join-menu]
|
||||
[status-im.contexts.communities.actions.share-community-channel.view :as share-community-channel]
|
||||
[status-im.contexts.communities.discover.view :as communities.discover]
|
||||
@@ -461,6 +463,10 @@
|
||||
{:modalPresentationStyle :overCurrentContext})
|
||||
:component scan-profile-qr-page/view}
|
||||
|
||||
{:name :invite-people-community
|
||||
:options {:sheet? true}
|
||||
:component communities.invite/view}
|
||||
|
||||
;; Settings
|
||||
|
||||
{:name :settings-password
|
||||
|
||||
@@ -183,7 +183,15 @@
|
||||
:synced-to
|
||||
:synced-from
|
||||
:community-id
|
||||
:emoji])))
|
||||
:emoji
|
||||
:description
|
||||
:last-message])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/current-chat-color
|
||||
:<- [:chats/current-raw-chat]
|
||||
(fn [current-chat]
|
||||
(:color current-chat)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/community-channel-ui-details-by-id
|
||||
|
||||
@@ -253,6 +253,13 @@
|
||||
multiaccount
|
||||
(contact.db/find-contact-by-address contacts address))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/contact-customization-color-by-address
|
||||
(fn [[_ address]]
|
||||
[(re-frame/subscribe [:contacts/contact-by-address address])])
|
||||
(fn [[contact]]
|
||||
(:customization-color contact)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:contacts/filtered-active-sections
|
||||
:<- [:contacts/active-sections]
|
||||
|
||||
@@ -92,6 +92,12 @@
|
||||
(fn [profile]
|
||||
(:test-networks-enabled? profile)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:profile/universal-profile-url
|
||||
:<- [:profile/profile]
|
||||
(fn [profile]
|
||||
(:universal-profile-url profile)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:profile/is-goerli-enabled?
|
||||
:<- [:profile/profile]
|
||||
|
||||
@@ -500,9 +500,8 @@ class CommunityView(HomeView):
|
||||
community_element.long_press_until_element_is_shown(self.share_community_button)
|
||||
self.share_community_button.click()
|
||||
for user_name in user_names_to_share:
|
||||
user_contact = self.element_by_text_part(user_name)
|
||||
user_contact.scroll_and_click()
|
||||
self.share_community_link_button.click()
|
||||
Button(self.driver, xpath="//*[@content-desc='author-primary-name'][@text='%s']" % user_name).click()
|
||||
self.setup_chat_button.click()
|
||||
|
||||
|
||||
class PreviewMessage(ChatElementByText):
|
||||
|
||||
+10
-2
@@ -154,6 +154,7 @@
|
||||
"changed-amount-warning": "Amount was changed from {{old}} to {{new}}",
|
||||
"changed-asset-warning": "Asset was changed from {{old}} to {{new}}",
|
||||
"channel-on-status": "Channel on Status",
|
||||
"community-on-status": "Community on Status",
|
||||
"chaos-mode": "Chaos mode",
|
||||
"chaos-unicorn-day": "Chaos Unicorn Day",
|
||||
"chaos-unicorn-day-details": "🦄🦄🦄🦄🦄🦄🦄🚀!",
|
||||
@@ -605,7 +606,7 @@
|
||||
"skip": "Skip",
|
||||
"password-placeholder": "Password...",
|
||||
"confirm-password-placeholder": "Confirm your password...",
|
||||
"ens-or-chat-key": "ENS or Chat key",
|
||||
"ens-or-chat-key": "ENS or Chatkey",
|
||||
"user-found": "User found",
|
||||
"enter-pin": "Enter 6-digit passcode",
|
||||
"enter-puk-code": "Enter PUK code",
|
||||
@@ -2574,5 +2575,12 @@
|
||||
"display": "Display",
|
||||
"testnet-mode-enabled": "Testnet mode enabled",
|
||||
"online-community-member": "Online",
|
||||
"offline-community-member": "Offline"
|
||||
"offline-community-member": "Offline",
|
||||
"invite-to-community": "Invite to community",
|
||||
"invite-n-users": "Invite {{count}} users",
|
||||
"invite-1-user": "Invite 1 user",
|
||||
"one-user-was-invited": "1 user was invited",
|
||||
"n-users-were-invited": "{{count}} users were invited",
|
||||
"invite-friend-to-status": "Invite friends to Status",
|
||||
"send-community-link": "Send community link"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user