Compare commits

...
Author SHA1 Message Date
Volodymyr Kozieiev dbf12ac337 cleanup, linting 2024-04-19 16:34:32 +02:00
Volodymyr Kozieiev df80824933 forgotten file 2024-04-19 16:27:07 +02:00
Volodymyr Kozieiev 36d63036f4 cleanup 2024-04-19 16:25:27 +02:00
Volodymyr Kozieiev 727f473676 good enough refactoring 2024-04-19 15:56:13 +02:00
Volodymyr Kozieiev 1ad8916cad still good enough for PR) 2024-04-18 17:20:45 +02:00
Volodymyr Kozieiev dda6c14874 changes that are good enough for separate refactoring pr 2024-04-18 17:04:29 +02:00
Volodymyr Kozieiev 96538f722c common functionality for controlled input moved to utils directory 2024-04-18 12:21:46 +02:00
Volodymyr Kozieiev 33d8529c3b wip: popup created, long press added 2024-04-16 15:36:59 +02:00
Sean Hagstrom 61974dae4d Implement share button inside profile settings (#19559)
* tweak: implement the share button inside the profile settings page

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

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

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

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

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

* tweak: ensure navigation effects are loaded

* tidy: use open-share dispatch in ui

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

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

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

* tidy: replace strings with keywords

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

* tweak: rename :content to :options

* fix: conditionally call on-error

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

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

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

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

* fix(copy): Chat Key -> Chatkey

---------

Signed-off-by: yqrashawn <namy.19@gmail.com>
2024-04-12 20:25:04 +08:00
Ibrahem Khalil 6ba89d0c6e Community channel avatar is missing on Channel screen (#19459) 2024-04-12 01:18:21 +02:00
Ibrahem Khalil 2614131426 [18979] Implement context action Invite contacts to community (#18989) 2024-04-12 00:12:02 +02:00
99 changed files with 2209 additions and 1252 deletions
+8 -1
View File
@@ -36,6 +36,12 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Fetch') {
steps { script {
@@ -52,7 +58,8 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh 'pip3 install --user -r requirements.txt'
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
}
} }
}
+8 -1
View File
@@ -54,6 +54,12 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Prep') {
steps { script {
@@ -79,7 +85,8 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh 'pip3 install --user -r requirements.txt'
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
}
} }
}
+7 -1
View File
@@ -35,6 +35,11 @@ pipeline {
))
}
environment {
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
}
stages {
stage('Prep') {
@@ -51,7 +56,8 @@ pipeline {
stage('Setup') {
steps { script {
dir('test/appium') {
sh 'pip3 install --user -r requirements.txt'
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
}
} }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+9 -50
View File
@@ -4,6 +4,7 @@
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
legacy.status-im.communities.e2e
[re-frame.core :as re-frame]
[status-im.contexts.shell.activity-center.events :as activity-center]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
@@ -22,60 +23,10 @@
{}
requests))
(defn- fetch-community-id-input
[{:keys [db]}]
(:communities/community-id-input db))
(rf/defn handle-response
[_ response-js]
{:dispatch [:sanitize-messages-and-process-response response-js]})
(rf/defn invite-users
{:events [::invite-people-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_inviteUsersToCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-invite-people %]))}]})))
(rf/defn share-community
{:events [::share-community-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-share-community %]))}]})))
(re-frame/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {:invite? true}]]]}))
(re-frame/reg-event-fx :communities/share-community-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {}]]]}))
(rf/defn people-invited
{:events [::people-invited]}
[cofx response-js]
@@ -90,6 +41,14 @@
[:sanitize-messages-and-process-response response-js]
[:activity-center.notifications/fetch-unread-count]]}))
(rf/defn member-banned
{:events [::member-banned]}
[cofx response-js]
(rf/merge cofx
(bottom-sheet/hide-bottom-sheet-old)
(handle-response response-js)
(activity-center/notifications-fetch-unread-count)))
(rf/defn member-ban
{:events [::member-ban]}
[cofx community-id public-key]
@@ -2,7 +2,6 @@
(:require
[legacy.status-im.browser.core :as browser]
[legacy.status-im.browser.webview-ref :as webview-ref]
[legacy.status-im.qr-scanner.core :as qr-scanner]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.connectivity.view :as connectivity]
@@ -105,9 +104,7 @@
(if empty-tab
[react/touchable-highlight
{:accessibility-label :universal-qr-scanner
:on-press #(re-frame/dispatch
[::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}])}
:on-press #(re-frame/dispatch [:open-modal :shell-qr-reader])}
[icons/icon :main-icons/qr {:color colors/black}]]
[react/touchable-highlight
{:on-press #(re-frame/dispatch
@@ -1,16 +1,15 @@
(ns legacy.status-im.ui.screens.communities.invite
(:require
[clojure.string :as string]
[legacy.status-im.communities.core :as communities]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.toolbar :as toolbar]
[legacy.status-im.ui.components.topbar :as topbar]
[quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -49,18 +48,35 @@
contacts-selected (reagent/atom #{})
{:keys [invite?]} (rf/sub [:get-screen-params])]
(fn []
(let [contacts-data (rf/sub [:contacts/active])
{:keys [permissions
can-manage-users?]}
(let [theme (quo.theme/use-theme)
contacts-data (rf/sub [:contacts/active])
{community-id :id
:keys [permissions
can-manage-users?]}
(rf/sub [:communities/edited-community])
selected @contacts-selected
selected-contacts-count (count selected)
contacts (map (fn [{:keys [public-key] :as contact}]
(assoc contact :active (contains? selected public-key)))
contacts-data)
;; no-membership communities can only be shared
can-invite? (and can-manage-users?
invite?
(not= (:access permissions) constants/community-no-membership-access))]
(not= (:access permissions) constants/community-no-membership-access))
on-press-share-community (rn/use-callback
(fn []
(rf/dispatch [:communities/share-community-confirmation-pressed
selected community-id])
(rf/dispatch [:navigate-back])
(rf/dispatch [:toasts/upsert
{:type :positive
:theme theme
:text (if (= 1 selected-contacts-count)
(i18n/label :t/one-user-was-invited)
(i18n/label
:t/n-users-were-invited
{:count selected-contacts-count}))}]))
[community-id selected selected-contacts-count theme])]
[:<>
[topbar/topbar
{:title (i18n/label (if can-invite?
@@ -81,11 +97,8 @@
:center
[quo/button
{:disabled (and (string/blank? @user-pk)
(zero? (count selected)))
(zero? selected-contacts-count))
:accessibility-label :share-community-link
:type :secondary
:on-press #(debounce/throttle-and-dispatch
[::communities/share-community-confirmation-pressed @user-pk
selected]
3000)}
:on-press on-press-share-community}
(i18n/label (if can-invite? :t/invite :t/share))]}]]))))
@@ -13,3 +13,11 @@
:background-color (colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
theme)})
(defn avatar-identifier
[theme]
{:text-align :center
:font-size 36
:color (colors/theme-colors colors/black
colors/white
theme)})
@@ -1,7 +1,9 @@
(ns quo.components.avatars.group-avatar.view
(:require
[clojure.string :as string]
[quo.components.avatars.group-avatar.style :as style]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
@@ -21,7 +23,7 @@
(defn- view-internal
[_]
(fn [{:keys [size theme customization-color picture icon-name]
(fn [{:keys [size theme customization-color picture icon-name emoji chat-name]
:or {size :size-20
customization-color :blue
picture nil
@@ -38,8 +40,27 @@
{:source picture
:style {:width container-size
:height container-size}}]
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}])])))
(cond
emoji
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
emoji]
[text/text
{:size :paragraph-1
:style (dissoc (style/avatar-identifier theme) :font-size)}
emoji])
chat-name
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
((comp first string/upper-case) chat-name)]
[text/text
{:size :paragraph-1}
((comp first string/upper-case) chat-name)])
:else
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}]))])))
(def view (quo.theme/with-theme view-internal))
+7 -6
View File
@@ -102,7 +102,7 @@
:right 8})
(def token-tag-spacing
{:margin-top 10
{:padding-top 10
:margin-right 8})
(defn token-row
@@ -117,11 +117,12 @@
(defn token-row-or-text
[padding? theme]
(merge
{:margin-top 4
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)}
{:padding-top 4
:margin-bottom -2
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)}
(when padding?
{:padding-left 12})))
+8 -4
View File
@@ -1,7 +1,8 @@
(ns quo.components.inputs.input.style
(:require
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]))
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
(defn variants-colors
[blur? theme]
@@ -97,9 +98,12 @@
(assoc base-props
:text-align-vertical :top
:line-height 22)
(assoc base-props
:height (if small? 30 38)
:line-height nil))))
(cond-> base-props
:always
(assoc :height (if small? 30 38)
:line-height nil)
platform/ios?
(assoc :padding-top (+ padding 2))))))
(defn right-icon-touchable-area
[small?]
+7 -4
View File
@@ -41,21 +41,24 @@
(defn user
[{:keys [short-chat-key primary-name secondary-name photo-path online? contact? verified?
untrustworthy? on-press on-long-press accessory customization-color theme
allow-multiple-presses?]}]
allow-multiple-presses? disabled?]}]
[rn/touchable-highlight
{:style container-style
:underlay-color (colors/resolve-color customization-color theme 5)
:allow-multiple-presses? allow-multiple-presses?
:accessibility-label :user-list
:on-press (when on-press on-press)
:on-long-press (when on-long-press on-long-press)}
:on-long-press (when on-long-press on-long-press)
:disabled disabled?}
[:<>
[user-avatar/user-avatar
{:full-name primary-name
:profile-picture photo-path
:online? online?
:size :small}]
[rn/view {:style {:margin-horizontal 8 :flex 1}}
[rn/view
{:style {:margin-horizontal 8
:flex 1}}
[author/view
{:primary-name primary-name
:secondary-name secondary-name
@@ -69,4 +72,4 @@
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
short-chat-key])]
(when accessory
[action-icon accessory customization-color theme])]])
[action-icon accessory customization-color disabled? theme])]])
@@ -4,7 +4,6 @@
[:map
[:type {:optional true} [:enum :default :watch-only :add-account :empty :missing-keypair]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:theme :schema.common/theme]
[:metrics? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]])
@@ -11,7 +11,6 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- loading-view
@@ -96,87 +95,87 @@
:end {:x 1 :y 0}}])
(defn- user-account
[_]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)]
(fn [{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
theme on-press]}]
(let [watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? @pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? @pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))))
[{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
on-press]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))
watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))
(defn- add-account-view
[_]
(let [pressed? (reagent/atom false)]
(fn [{:keys [on-press customization-color theme metrics?]}]
[rn/pressable
{:on-press on-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? @pressed?})}
[button/button
{:type :primary
:size 24
:icon true
:accessibility-label :add-account
:on-press on-press
:pressed? @pressed?
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:customization-color customization-color
:icon-only? true}
:i/add]])))
[{:keys [on-press customization-color metrics?]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? pressed?})}
[button/button
{:on-press on-press
:type :primary
:size 24
:icon true
:accessibility-label :add-account
:pressed? pressed?
:icon-only? true
:customization-color customization-color}
:i/add]]))
(defn- view-internal
[{:keys [type] :as props}]
@@ -185,6 +184,4 @@
:add-account [add-account-view props]
nil))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
@@ -6,9 +6,7 @@
[:props
[:map {:closed true}
[:status {:optional true} [:maybe [:enum :default :error]]]
[:theme :schema.common/theme]
[:on-inc-press {:optional true} [:maybe fn?]]
[:on-dec-press {:optional true} [:maybe fn?]]
[:on-change-text {:optional true} [:maybe fn?]]
[:container-style {:optional true} [:maybe :map]]
[:min-value {:optional true} [:maybe :int]]
[:max-value {:optional true} [:maybe :int]]
@@ -4,7 +4,7 @@
[quo.components.markdown.text :as text]
[quo.components.wallet.amount-input.schema :as amount-input.schema]
[quo.components.wallet.amount-input.style :as style]
[quo.theme :as quo.theme]
[quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
@@ -21,35 +21,34 @@
icon])
(defn- view-internal
[{:keys [on-inc-press on-dec-press theme status value min-value max-value
[{:keys [on-inc-press on-dec-press status value min-value max-value
container-style]
:or {value 0
min-value 0
max-value 999999999}}]
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]])
(let [theme (quo.theme/use-theme-value)]
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal amount-input.schema/?schema)))
(def view (schema/instrument #'view-internal amount-input.schema/?schema))
+4 -5
View File
@@ -37,8 +37,9 @@
:border? true}]))
(defn title-view
[{:keys [details action selected? type blur? customization-color on-options-press theme]}]
(let [{:keys [full-name]} details]
[{:keys [details action selected? type blur? customization-color on-options-press]}]
(let [theme (quo.theme/use-theme-value)
{:keys [full-name]} details]
[rn/view
{:style style/title-container
:accessibility-label :title}
@@ -89,7 +90,7 @@
[item & _rest]
[account-list-card/view item])
(defn- view-internal
(defn view
[{:keys [accounts action container-style selected? on-press] :as props}]
[rn/pressable
{:style (style/container (merge props
@@ -108,5 +109,3 @@
:render-fn acc-list-card
:separator [rn/view {:style {:height 8}}]
:style {:padding-horizontal 8}}]])
(def view (quo.theme/with-theme view-internal))
@@ -27,14 +27,15 @@
:else (string/capitalize (name network))))
(defn view-internal
[{:keys [theme network status amount container-style on-press] :as args}]
[{:keys [theme network status amount container-style on-press on-long-press] :as args}]
(if (= status :add)
[network-bridge-add args]
[rn/pressable
{:style (merge (style/container network status theme) container-style)
:accessible true
:accessibility-label :container
:on-press on-press}
:on-press on-press
:on-long-press on-long-press}
(if (= status :loading)
[rn/view
{:style (style/loading-skeleton theme)
@@ -0,0 +1,23 @@
(ns quo.components.wallet.network-link.helpers)
(def ^:private central-figure-width 63)
(defn calculate-side-lines-path-1x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 57 L" side-offset " 57")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))
(defn calculate-transform
"Calculates the transform attribute for the central figure based on the SVG width."
[width]
(let [translate-x (/ (- width central-figure-width) 2)]
(str "translate(" translate-x " 0)")))
(defn calculate-side-lines-path-2x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 113 L" side-offset " 113")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))
@@ -0,0 +1,34 @@
(ns quo.components.wallet.network-link.style)
(def left-circle-container
{:position :absolute
:left -3})
(def right-circle-container
{:position :absolute
:right -3})
(def bottom-left-circle-container
{:position :absolute
:bottom -3
:left -3})
(def top-right-circle-container
{:position :absolute
:top -3
:right -3})
(def link-linear-container
{:flex-direction :row
:align-items :center
:height 10})
(def link-1x-container
{:flex 1
:height 58
:justify-content :center})
(def link-2x-container
{:flex 1
:height 114
:justify-content :center})
+134 -65
View File
@@ -1,82 +1,151 @@
(ns quo.components.wallet.network-link.view
(:require
[oops.core :refer [oget]]
[quo.components.wallet.network-link.helpers :as helpers]
[quo.components.wallet.network-link.schema :as component-schema]
[quo.components.wallet.network-link.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- circle
[fill stroke]
[svg/svg
{:height "8"
:width "8"}
[svg/circle
{:cx "4"
:cy "4"
:r "3.5"
:fill fill
:stroke stroke
:strokeWidth "1"}]])
(defn- line
[stroke width]
[svg/svg
{:height "10"
:width "100%"
:view-box (str "0 0 " width " 10")}
[svg/path
{:d (str "M0,5 L" width ",5")
:stroke stroke
:stroke-width "1"}]])
(defn link-linear
[{:keys [source theme]}]
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "10" :fill :none}
[svg/path {:stroke (colors/resolve-color source theme) :d "M68 5H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/circle
{:cx "5"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]])
[]
(let [container-width (reagent/atom 100)]
(fn [{:keys [source theme]}]
(let [stroke-color (colors/resolve-color source theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)]
[rn/view
{:style style/link-linear-container
:on-layout (fn [e]
(reset! container-width
(oget e :nativeEvent :layout :width)))}
[line stroke-color @container-width]
[rn/view {:style style/left-circle-container}
[circle fill-color stroke-color]]
[rn/view {:style style/right-circle-container}
[circle fill-color stroke-color]]]))))
(defn link-1x
[{:keys [source destination theme]}]
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "66" :fill :none}
[svg/path
{:stroke "url(#gradient)" :d "M68 5h-9.364c-11.046 0-20 8.954-20 20v16c0 11.046-8.955 20-20 20H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "61"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient" :x1 "72.271" :x2 "82.385" :y1 "5" :y2 "34.155" :gradientUnits "userSpaceOnUse"}
[svg/stop {:stopColor (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stopColor (colors/resolve-color source theme)}]]]])
[]
(let [container-width (reagent/atom 100)
stroke-color "url(#gradient)"]
(fn [{:keys [source destination theme]}]
(let [source-color (colors/resolve-color source theme)
destination-color (colors/resolve-color destination theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
view-box (str "0 0 " @container-width " 58")
side-lines-path (helpers/calculate-side-lines-path-1x @container-width)
central-transform (helpers/calculate-transform @container-width)]
[rn/view
{:style style/link-1x-container
:on-layout (fn [e]
(reset! container-width
(oget e :nativeEvent :layout :width)))}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M63 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 37C33.6356 48.0457 24.6813 57 13.6356 57L2.85889e-05 57"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.271"
:x2 "82.385"
:y1 "5"
:y2 "34.155"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))))
(defn link-2x
[{:keys [source destination theme]}]
[svg/svg
{:width "73" :height "122" :viewBox "0 0 73 122" :fill "none" :xmlns "http://www.w3.org/2000/svg"}
[svg/path
{:d
"M67.9999 5L58.6356 5C47.5899 5 38.6356 13.9543 38.6356 25L38.6356 97C38.6356 108.046 29.6813 117 18.6356 117L5.00006 117"
:stroke "url(#gradient)"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "117"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradientUnits "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]])
[]
(let [container-width (reagent/atom 100)
stroke-color "url(#gradient)"]
(fn [{:keys [source destination theme]}]
(let [source-color (colors/resolve-color source theme)
destination-color (colors/resolve-color destination theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
view-box (str "0 0 " @container-width " 114")
side-lines-path (helpers/calculate-side-lines-path-2x @container-width)
central-transform (helpers/calculate-transform @container-width)]
[rn/view
{:style style/link-2x-container
:on-layout #(reset! container-width
(oget % :nativeEvent :layout :width))}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M62.9999 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 93C33.6356 104.046 24.6813 113 13.6356 113L5.71778e-05 113"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))))
(defn- view-internal
[{:keys [shape container-style] :as props}]
@@ -12,6 +12,5 @@
[:amount :int]
[:max-amount :int]
[:network-name [:or :string :keyword]]]]]]
[:container-style {:optional true} [:maybe :map]]
[:theme :schema.common/theme]]]]
[:container-style {:optional true} [:maybe :map]]]]]
:any])
@@ -29,13 +29,13 @@
:background-color (colors/resolve-color network-name nil 10)})
(defn network-bar
[{:keys [max-width on-top? bar-division? theme]
[{:keys [bar-max-width on-top? bar-division? theme]
{:keys [network-name translate-x-shared-value]} :bar}
width-shared-value]
(reanimated/apply-animations-to-style
{:width width-shared-value
:transform [{:translate-x translate-x-shared-value}]}
{:max-width max-width
{:max-width bar-max-width
:flex-direction :row
:justify-content :flex-end
:background-color (colors/resolve-color network-name nil)
@@ -8,7 +8,6 @@
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[schema.core :as schema]
[utils.number]))
@@ -22,60 +21,73 @@
(js/clearTimeout (k @timeouts))
(swap! timeouts assoc k (js/setTimeout exec-fn-and-remove-timeout ms))))
(defn- f-slider
[slider-shared-values]
[rn/view {:style style/slider-container}
[reanimated/view {:style (style/slider slider-shared-values)}]])
(def get-gesture
(memoize
(fn
[[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture]]
(-> (gesture/gesture-pan)
(gesture/enabled detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(set-detecting-gesture false) 20)))))))
(defn f-network-bar
[_]
(let [detecting-gesture? (reagent/atom false)
amount-on-gesture-start (atom 0)]
(fn [{:keys [total-width total-amount on-press on-new-amount allow-press?]
{:keys [amount-shared-value
max-amount]} :bar
:as props}]
(let [slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)]
[rn/pressable
{:on-press (fn []
(when (and (not @detecting-gesture?) allow-press?)
(on-press)
(reset! detecting-gesture? true)
(animation/show-slider slider-opacity-shared-value)))}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector
{:gesture
(-> (gesture/gesture-pan)
(gesture/enabled @detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(reset! detecting-gesture? false) 20))))}
[:f> f-slider
{:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value}]]]]))))
(defn network-bar
[{:keys [total-width total-amount on-press on-new-amount allow-press?
bar-idx bar-width bar-max-width]
{:keys [amount-shared-value
max-amount]
:as bar} :bar
:as props}]
(let [[detecting-gesture?
set-detecting-gesture] (rn/use-state false)
amount-on-gesture-start (rn/use-ref-atom 0)
slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)
gesture (get-gesture
[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture])
on-press (rn/use-callback
(fn []
(when (and (not detecting-gesture?) allow-press?)
(on-press bar bar-idx bar-width bar-max-width)
(set-detecting-gesture true)
(animation/show-slider slider-opacity-shared-value)))
[detecting-gesture? allow-press? on-press])]
[rn/pressable
{:on-press on-press}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector {:gesture gesture}
[rn/view {:style style/slider-container}
[reanimated/view
{:style (style/slider {:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value})}]]]]]))
(defn- add-bar-shared-values
[{:keys [amount] :as network}]
@@ -93,101 +105,113 @@
(interleave (repeat [rn/view {:style (style/dashed-line-line network-name)}])
(repeat [rn/view {:style style/dashed-line-space}])))))
(defn f-network-routing-bars
[_]
(let [selected-network-idx (reagent/atom nil)
press-locked? (reagent/atom false)
lock-press #(reset! press-locked? true)
unlock-press #(reset! press-locked? false)
reset-state-values #(reset! selected-network-idx nil)]
(fn [{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))]
(rn/use-effect
#(when (and (not requesting-data?) @selected-network-idx)
(let [bar (nth network-bars @selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) @selected-network-idx)
this-bar-selected? (= @selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[:f> f-network-bar
{:bar bar
:max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not @selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not @press-locked?))
:on-press (fn []
(when-not @selected-network-idx
(let [[previous-bars [_ & next-bars]] (split-at bar-idx network-bars)
number-previous-bars bar-idx]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars number-previous-bars})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc number-previous-bars)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(reset! selected-network-idx bar-idx)))
:on-new-amount (fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount @selected-network-idx)))}]))
(defn- network-routing-bars
[{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [[selected-network-idx
set-selected-network-idx] (rn/use-state nil)
[press-locked?
set-press-locked] (rn/use-state false)
lock-press (rn/use-callback #(set-press-locked true))
unlock-press (rn/use-callback #(set-press-locked false))
reset-state-values (rn/use-callback #(set-selected-network-idx nil))
bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))
network-bar-on-press (rn/use-callback
(fn [bar bar-idx bar-width bar-max-width]
(when-not selected-network-idx
(let [[previous-bars
[_ & next-bars]] (split-at bar-idx network-bars)]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars bar-idx})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc bar-idx)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(set-selected-network-idx bar-idx)))
[selected-network-idx bars-widths-negative network-bars])
on-new-amount (rn/use-callback
(fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount selected-network-idx)))
[on-amount-selected selected-network-idx])]
(rn/use-effect
#(when (and (not requesting-data?) selected-network-idx)
(let [bar (nth network-bars selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) selected-network-idx)
this-bar-selected? (= selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[network-bar
{:bar bar
:bar-idx bar-idx
:bar-width bar-width
:bar-max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not press-locked?))
:on-press network-bar-on-press
:on-new-amount on-new-amount}]))
(let [{:keys [max-amount network-name]} (some->> @selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))))
(let [{:keys [max-amount network-name]} (some->> selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))
(defn view-internal
[{:keys [networks container-style theme] :as params}]
(reagent/with-let [total-width (reagent/atom nil)]
[{:keys [networks container-style] :as params}]
(let [theme (quo.theme/use-theme-value)
[total-width
set-total-width] (rn/use-state nil)
on-layout (rn/use-callback #(let [width (oops/oget % "nativeEvent.layout.width")]
(when (not= width total-width)
(set-total-width width))))]
(rn/use-unmount (fn []
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout))))
[rn/view
{:accessibility-label :network-routing
:style (style/container container-style theme)
:on-layout #(reset! total-width (oops/oget % "nativeEvent.layout.width"))}
(when @total-width
:on-layout on-layout}
(when total-width
^{:key (str "network-routing-" (count networks))}
[:f> f-network-routing-bars (assoc params :total-width @total-width)])]
(finally
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout)))))
[network-routing-bars (assoc params :total-width total-width)])]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal network-routing-schema/?schema)))
(def view (schema/instrument #'view-internal network-routing-schema/?schema))
@@ -14,6 +14,5 @@
[:networks {:optional true}
[:maybe [:sequential [:map [:source [:maybe :schema.common/image-source]]]]]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:value {:optional true} [:maybe :string]]
[:theme :schema.common/theme]]]]
[:value {:optional true} [:maybe :string]]]]]
:any])
+38 -35
View File
@@ -12,7 +12,6 @@
[quo.foundations.common :as common]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn fiat-format
@@ -75,7 +74,8 @@
[token-name-text theme text]])
(defn input-section
[{:keys [on-change-text value value-atom on-selection-change on-token-press]}]
[{:keys [on-change-text value value-internal set-value-internal on-selection-change
on-token-press]}]
(let [input-ref (atom nil)
set-ref #(reset! input-ref %)
focus-input #(when-let [ref ^js @input-ref]
@@ -83,7 +83,7 @@
controlled-input? (some? value)
handle-on-change-text (fn [v]
(when-not controlled-input?
(reset! value-atom v))
(set-value-internal v))
(when on-change-text
(on-change-text v)))
handle-selection-change (fn [^js e]
@@ -117,41 +117,44 @@
: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}]
(tap> 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))
+4 -1
View File
@@ -10,7 +10,10 @@
(def ui-themed
{:no-funds
{:light (js/require "../resources/images/ui2/no-funds-light.png")
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}})
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}
:no-contacts-to-chat
{:light (js/require "../resources/images/ui2/no-contacts-to-chat-light.png")
:dark (js/require "../resources/images/ui2/no-contacts-to-chat-dark.png")}})
(defn get-themed-image
[k theme]
-4
View File
@@ -226,7 +226,3 @@
(defn open-url [link] (.openURL ^js linking link))
(def set-status-bar-style react-native/StatusBar.setBarStyle)
(defn sharing
[content]
(.share (.-Share ^js react-native) (clj->js content)))
+1 -5
View File
@@ -4,9 +4,5 @@
(defn open
([options]
(open options #() #()))
([options on-success on-error]
(-> ^js react-native-share
(.open (clj->js options))
(.then on-success)
(.catch on-error))))
(.open (clj->js options)))))
+1 -1
View File
@@ -12,5 +12,5 @@
(defn contacts-section-header
[{:keys [title]}]
(let [theme (quo.theme/use-theme-value)]
[quo/divider-label {:container-style {:background-color (style/contacts-section-header theme)}}
[quo/divider-label {:container-style (style/contacts-section-header theme)}
title]))
@@ -5,7 +5,7 @@
[utils.re-frame :as rf]))
(defn contact-list-item
[{:keys [on-press on-long-press accessory allow-multiple-presses?]}
[{:keys [on-press on-long-press accessory allow-multiple-presses? disabled?]}
{:keys [primary-name secondary-name public-key compressed-key ens-verified added?]}
theme]
(let [photo-path (rf/sub [:chats/photo-path public-key])
@@ -24,4 +24,5 @@
:contact? added?
:on-press on-press
:on-long-press on-long-press
:accessory accessory}]))
:accessory accessory
:disabled? disabled?}]))
@@ -0,0 +1,114 @@
(ns status-im.common.controlled-input.utils
(:require
[clojure.string :as string]
[reagent.core :as reagent]))
(defn create-input-state
[]
(reagent/atom {:value ""
:error? false
:upper-limit nil}))
(defn input-value
[state]
(:value @state))
(defn numeric-value
[state]
(or (parse-double (input-value state)) 0))
(defn input-error
[state]
(:error? @state))
(defn- set-input-error
[state error?]
(swap! state assoc :error? error?))
(defn- upper-limit
[state]
(:upper-limit @state))
(defn upper-limit-exceeded?
[state]
(and
(upper-limit state)
(> (numeric-value state) (upper-limit state))))
(defn- recheck-errorness
[state]
(set-input-error state (upper-limit-exceeded? state)))
(defn- set-input-value
[state value]
(swap! state assoc :value value)
(recheck-errorness state))
(defn set-upper-limit
[state limit]
(swap! state assoc :upper-limit limit)
(recheck-errorness state))
(def not-digits-or-dot-pattern
#"[^0-9+\.]")
(def dot ".")
(defn- can-add-character?
[state c]
(let [max-length 12
current (input-value state)
length-overflow? (>= (count current) max-length)
extra-dot? (and (= c dot) (string/includes? current dot))
extra-leading-zero? (and (= current "0") (= "0" (str c)))
non-numeric? (re-find not-digits-or-dot-pattern (str c))]
(not (or non-numeric? extra-dot? extra-leading-zero? length-overflow?))))
(defn- normalize-value-as-numeric
[value c]
(cond
(and (string/blank? value) (= c dot))
(str "0" c)
(and (= value "0") (not= c dot))
(str c)
:else
(str value c)))
(defn add-character
[state c]
(when (can-add-character? state c)
(set-input-value state
(normalize-value-as-numeric (input-value state) c))))
(defn delete-last
[state]
(let [value (input-value state)
new-value (subs value 0 (dec (count value)))]
(set-input-value state new-value)))
(defn delete-all
[state]
(set-input-value state ""))
(comment
(def s "")
(subs s 0 (dec (count s)))
(def s (create-input-state))
(input-value s)
(input-error s)
(set-input-value s "51")
(set-upper-limit s 1)
(set-input-error s true)
(add-character s "2")
(set-input-value s "")
(delete-last s)
(delete-all s)
(def a 5)
s)
+6 -5
View File
@@ -3,7 +3,6 @@
[react-native.cameraroll :as cameraroll]
[react-native.fs :as fs]
[react-native.platform :as platform]
[react-native.share :as share]
[utils.re-frame :as rf]))
(def config
@@ -15,10 +14,12 @@
(blob/fetch uri
config
(fn [downloaded-url]
(share/open {:url (str (when platform/android? "file://") downloaded-url)
:isNewTask true}
#(fs/unlink downloaded-url)
#(fs/unlink downloaded-url))))))
(rf/dispatch [:open-share
{:options {:url (str (when platform/android? "file://")
downloaded-url)
:isNewTask true}
:on-success #(fs/unlink downloaded-url)
:on-error #(fs/unlink downloaded-url)}])))))
(rf/reg-fx :effects.lightbox/save-image-to-gallery
(fn [[uri on-success]]
@@ -18,8 +18,8 @@
[rn/view
[quo/text
{:accessibility-label :sync-code-generated
:weight :bold
:size :heading-1
:weight :semi-bold
:size :heading-2
:style {:margin-bottom 4}}
(i18n/label :t/enter-password)]
[rn/view
+2
View File
@@ -448,6 +448,8 @@
(def ^:const optimism-short-name "opt")
(def ^:const arbitrum-short-name "arb1")
(def ^:const default-multichain-address-prefix "eth:opt:arb1:")
(def ^:const mainnet-abbreviated-name "Eth.")
(def ^:const optimism-abbreviated-name "Opt.")
(def ^:const arbitrum-abbreviated-name "Arb1.")
@@ -101,6 +101,7 @@
{:keys [primary-name public-key]} (when one-contact-selected?
(rf/sub [:contacts/contact-by-identity
(first selected-contacts)]))]
(rn/use-unmount #(rf/dispatch [:group-chat/clear-contacts]))
[rn/view {:flex 1}
[rn/view {:padding-horizontal 20}
[quo/button
+1 -2
View File
@@ -5,7 +5,6 @@
[re-frame.core :as re-frame]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.share :as share]
[status-im.common.contact-list-item.view :as contact-list-item]
[status-im.common.contact-list.view :as contact-list]
[status-im.common.home.actions.view :as actions]
@@ -122,7 +121,7 @@
[:show-bottom-sheet {:content chat.actions.view/new-chat}])
:accessibility-label :new-chat-button}
:card-props
{:on-press #(share/open {:url profile-link})
{:on-press #(rf/dispatch [:open-share {:options {:url profile-link}}])
:banner (resources/get-image :invite-friends)
:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}})
@@ -41,8 +41,13 @@
(defn f-send-button
[props state animations window-height images? btn-opacity z-index edit]
(let [{:keys [text-value]} state
customization-color (rf/sub [:profile/customization-color])]
(let [{:keys [text-value]} state
profile-customization-color (rf/sub [:profile/customization-color])
{:keys [chat-id chat-type]
chat-color :color} (rf/sub [:chats/current-chat-chat-view])
contact-customization-color (when (= chat-type constants/one-to-one-chat-type)
(rf/sub [:contacts/contact-customization-color-by-address
chat-id]))]
(rn/use-effect (fn []
;; Handle send button opacity animation and z-index when input content changes
(if (or (seq @text-value) images?)
@@ -60,7 +65,7 @@
[quo/button
{:icon-only? true
:size 32
:customization-color customization-color
:customization-color (or contact-customization-color chat-color profile-customization-color)
:accessibility-label :send-message-button
:on-press #(send-message props state animations window-height edit)}
:i/arrow-up]]))
@@ -29,11 +29,15 @@
(defn pinned-message
[{:keys [from quoted-message timestamp-str]}]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
customization-color (rf/sub [:profile/customization-color])]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
one-to-one-chat? (rf/sub [:chats/current-chat-one-to-one?])
current-chat-color (rf/sub [:chats/current-chat-color])
contact-customization-color (rf/sub [:contacts/contact-customization-color-by-address from])]
[quo/system-message
{:type :pinned
:pinned-by primary-name
:customization-color customization-color
:customization-color (if one-to-one-chat?
contact-customization-color
current-chat-color)
:child [reply/quoted-message quoted-message false true]
:timestamp timestamp-str}]))
@@ -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))
+76 -39
View File
@@ -1,11 +1,8 @@
(ns status-im.contexts.communities.events
(:require
[clojure.string :as string]
[legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.data-store.communities :as data-store.communities]
[legacy.status-im.mailserver.core :as mailserver]
[react-native.platform :as platform]
[react-native.share :as share]
[schema.core :as schema]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
@@ -14,9 +11,9 @@
status-im.contexts.communities.actions.airdrop-addresses.events
status-im.contexts.communities.actions.community-options.events
status-im.contexts.communities.actions.leave.events
[status-im.contexts.communities.utils :as utils]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn handle-community
@@ -152,43 +149,83 @@
:on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %])
:on-error #(log/error "failed to fetch collapsed community categories" %)}]}))
(rf/reg-event-fx :communities/get-community-channel-share-data
(fn [_ [chat-id on-success]]
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
{:json-rpc/call
[{:method "wakuext_shareCommunityChannelURLWithData"
:params [{:CommunityID community-id :ChannelID channel-id}]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community channel url with data"
{:error err
:chat-id chat-id
:event :communities/get-community-channel-share-data}))}]})))
(defn update-previous-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
(let [accounts (utils/sorted-non-watch-only-accounts db)
selected-permission-addresses (get-in db
[:communities community-id
:selected-permission-addresses])
selected-accounts (filter #(contains? selected-permission-addresses (:address %))
accounts)
current-airdrop-address (get-in db [:communities community-id :airdrop-address])
share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])]
{:db (update-in db
[:communities community-id]
assoc
:previous-share-all-addresses? share-all-addresses?
:previous-permission-addresses selected-permission-addresses
:airdrop-address (if (contains? selected-permission-addresses
current-airdrop-address)
current-airdrop-address
(:address (first selected-accounts))))})))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content title}
:item {:default {:type "url"
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/update-previous-permission-addresses
update-previous-permission-addresses)
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
(fn [_ [chat-id]]
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
{:chat-id chat-id
:url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(defn toggle-selected-permission-address
[{:keys [db]} [address community-id]]
(let [selected-permission-addresses
(get-in db [:communities community-id :selected-permission-addresses])
updated-selected-permission-addresses
(if (contains? selected-permission-addresses address)
(disj selected-permission-addresses address)
(conj selected-permission-addresses address))]
{:db (assoc-in db
[:communities community-id :selected-permission-addresses]
updated-selected-permission-addresses)
:fx [(when community-id
[:dispatch
[:communities/check-permissions-to-join-community community-id
updated-selected-permission-addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-selected-permission-address
toggle-selected-permission-address)
(defn toggle-share-all-addresses
[{:keys [db]} [community-id]]
(let [share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])
next-share-all-addresses? (not share-all-addresses?)
accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (update-in db
[:communities community-id]
assoc
:share-all-addresses? next-share-all-addresses?
:selected-permission-addresses addresses)
:fx [(when (and community-id next-share-all-addresses?)
[:dispatch
[:communities/check-permissions-to-join-community community-id
addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-share-all-addresses
toggle-share-all-addresses)
(rf/reg-event-fx :communities/reset-selected-permission-addresses
(fn [{:keys [db]} [community-id]]
(when community-id
{:db (update-in db
[:communities community-id]
assoc
:selected-permission-addresses
(get-in db [:communities community-id :previous-permission-addresses])
:share-all-addresses?
(get-in db [:communities community-id :previous-share-all-addresses?]))
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(rf/reg-event-fx :communities/set-airdrop-address
(fn [{:keys [db]} [address community-id]]
{:db (assoc-in db [:communities community-id :airdrop-address] address)}))
(defn community-fetched
[{:keys [db]} [community-id community]]
@@ -11,7 +11,7 @@
(def community-tag-container
{:padding-horizontal screen-horizontal-padding
:margin-horizontal (- screen-horizontal-padding)
:margin-bottom 20})
:margin-bottom 16})
(def community-content-container
{:padding-horizontal screen-horizontal-padding})
@@ -64,4 +64,5 @@
{:border-radius 16
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80)
:border-width 1
:padding-top 10})
:padding-top 10
:margin-bottom 106})
@@ -38,8 +38,9 @@
{:keys [name emoji muted? id mentions-count unread-messages? on-press locked? color] :as chat}]
(let [sheet-content [actions/chat-actions
(assoc chat
:chat-type constants/community-chat-type
:chat-id (str community-id id))
:community-id community-id
:chat-type constants/community-chat-type
:chat-id (str community-id id))
false]
notification (cond
muted? :mute
@@ -161,7 +162,7 @@
(i18n/label :t/you-eligible-to-join-as {:role highest-role-text})
(i18n/label :t/you-not-eligible-to-join))]
[info-button]]
[quo/text {:style {:padding-horizontal 12 :padding-bottom 18} :size :paragraph-2}
[quo/text {:style {:padding-horizontal 12 :padding-bottom 6} :size :paragraph-2}
(if can-request-access?
(i18n/label :t/you-hodl)
(i18n/label :t/you-must-hold))]
@@ -175,7 +176,7 @@
#(rf/dispatch [:open-modal :community-requests-to-join {:id id}]))
:accessibility-label :join-community-button
:customization-color color
:container-style {:margin-horizontal 12 :margin-top 12 :margin-bottom 12}
:container-style {:margin-horizontal 12 :margin-top 8 :margin-bottom 12}
:disabled? (not can-request-access?)
:icon-left (if can-request-access? :i/unlocked :i/locked)}
(i18n/label :t/request-to-join)]])))
@@ -0,0 +1,101 @@
(ns status-im.contexts.communities.sharing.events
(:require [legacy.status-im.data-store.chats :as data-store.chats]
[react-native.platform :as platform]
[react-native.share :as share]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :invite-people-community {:id id}]]]}))
(rf/reg-event-fx :communities/share-community-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {:id id}]]]}))
(rf/reg-event-fx :communities/share-community-confirmation-pressed
(fn [_ [users-public-keys community-id]]
{:fx [[:json-rpc/call
[{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users users-public-keys}]
:js-response true
:on-success [:sanitize-messages-and-process-response]
:on-error (fn [err]
(log/error {:message "failed to share community"
:community-id community-id
:err err}))}]]]}))
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
(fn [_ [chat-id]]
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
{:chat-id chat-id
:url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(rf/dispatch [:open-share
{:options (if platform/ios?
{:activityItemSources
[{:placeholderItem {:type :text
:content title}
:item {:default {:type :url
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})}]))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/get-community-channel-share-data
(fn [_ [chat-id on-success]]
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
{:json-rpc/call
[{:method "wakuext_shareCommunityChannelURLWithData"
:params [{:CommunityID community-id :ChannelID channel-id}]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community channel url with data"
{:error err
:chat-id chat-id
:event :communities/get-community-channel-share-data}))}]})))
(rf/reg-event-fx :communities/get-community-share-data
(fn [_ [community-id on-success]]
{:json-rpc/call
[{:method "wakuext_shareCommunityURLWithData"
:params [community-id]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community url with data"
{:error err
:community-id community-id
:event :communities/get-community-share-data}))}]}))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content title}
:item {:default {:type "url"
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
@@ -1,6 +1,7 @@
(ns status-im.contexts.preview.quo.wallet.network-link
(:require
[quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.preview.quo.preview :as preview]))
@@ -23,17 +24,21 @@
:options networks}
{:key :destination
:type :select
:options networks}])
:options networks}
{:key :width
:type :number}])
(defn view
[]
(let [state (reagent/atom {:shape :linear
:source :ethereum
:destination :optimism})]
:destination :optimism
:width 63})]
(fn []
[preview/preview-container
{:state state
:descriptor descriptor
:component-container-style {:padding-top 40
:align-items :center}}
[quo/network-link @state]])))
[rn/view {:style {:width (max (:width @state) 63)}}
[quo/network-link @state]]])))
@@ -48,7 +48,8 @@
(fn []
(rf/dispatch [:universal-links/generate-profile-url
{:public-key public-key
:on-success #(rn/sharing {:message %})}]))
:on-success #(rf/dispatch [:open-share
{:options {:message %}}])}]))
[public-key])
on-remove-contact (rn/use-callback
(fn []
@@ -1,6 +1,5 @@
(ns status-im.contexts.profile.contact.share.view
(:require [legacy.status-im.ui.components.list-selection :as list-selection]
[quo.core :as quo]
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.qr-codes.view :as qr-codes]
@@ -19,7 +18,8 @@
abbreviated-url (rn/use-memo (fn []
(address/get-abbreviated-profile-url universal-profile-url))
[universal-profile-url])
on-share-press (rn/use-callback #(list-selection/open-share {:message universal-profile-url})
on-share-press (rn/use-callback #(rf/dispatch [:open-share
{:options {:message universal-profile-url}}])
[universal-profile-url])
on-copy-press (rn/use-callback (fn []
(rf/dispatch [:share/copy-text-and-show-toast
@@ -34,7 +34,7 @@
(when config/show-not-implemented-features?
{:title (i18n/label :t/dapps)
:on-press not-implemented/alert
:image-props :i/placeholder
:image-props :i/dapps
:image :icon
:blur? true
:action :arrow})
@@ -5,7 +5,6 @@
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.common.not-implemented :as not-implemented]
[status-im.contexts.profile.settings.header.view :as settings.header]
[status-im.contexts.profile.settings.list-items :as settings.items]
[status-im.contexts.profile.settings.style :as style]
@@ -19,7 +18,7 @@
[rf/delay-render
[quo/category
{:list-type :settings
:container-style {:padding-bottom 0}
:container-style {:padding-bottom 12}
:blur? true
:data data}]])
@@ -62,7 +61,10 @@
:on-press #(rf/dispatch [:navigate-back])
:right-side [{:icon-name :i/qr-code
:on-press #(debounce/throttle-and-dispatch [:open-modal :share-shell] 1000)}
{:icon-name :i/share :on-press not-implemented/alert}]}]]
{:icon-name :i/share
:on-press #(rf/dispatch [:open-share
{:options {:message (:universal-profile-url
profile)}}])}]}]]
[rn/flat-list
{:key :list
:header [settings.header/view {:scroll-y scroll-y}]
@@ -22,13 +22,15 @@
(defn floating-button
[shared-values]
[quo/floating-shell-button
{:jump-to {:on-press #(animation/close-home-stack true)
:label (i18n/label :t/jump-to)
:customization-color (rf/sub [:profile/customization-color])}}
{:position :absolute
:bottom (utils/bottom-tabs-container-height)}
(:home-stack-opacity shared-values)])
(let [current-screen-id (rf/sub [:view-id])]
(when-not (= current-screen-id :settings)
[quo/floating-shell-button
{:jump-to {:on-press #(animation/close-home-stack true)
:label (i18n/label :t/jump-to)
:customization-color (rf/sub [:profile/customization-color])}}
{:position :absolute
:bottom (utils/bottom-tabs-container-height)}
(:home-stack-opacity shared-values)])))
(defn f-shell-stack
[]
@@ -1,7 +1,6 @@
(ns status-im.contexts.shell.share.profile.view
(:require
[clojure.string :as string]
[legacy.status-im.ui.components.list-selection :as list-selection]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
@@ -30,7 +29,7 @@
:width (- window-width (* style/screen-padding 2))
:qr-data universal-profile-url
:qr-data-label-shown abbreviated-url
:on-share-press #(list-selection/open-share {:message universal-profile-url})
:on-share-press #(rf/dispatch [:open-share {:options {:message universal-profile-url}}])
:on-text-press #(rf/dispatch [:share/copy-text-and-show-toast
{:text-to-copy universal-profile-url
:post-copy-message (i18n/label :t/link-to-profile-copied)}])
@@ -4,7 +4,6 @@
[quo.core :as quo]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.share :as share]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.shell.share.style :as style]
@@ -20,18 +19,18 @@
(defn- share-action
[address share-title]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content address}
:item {:default {:type "text"
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})))
(rf/dispatch [:open-share
{:options (if platform/ios?
{:activityItemSources [{:placeholderItem {:type :text
:content address}
:item {:default {:type :text
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})}]))
(defn- open-preferences
[selected-networks account]
@@ -4,7 +4,6 @@
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[react-native.share :as share]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.wallet.account.share-address.style :as style]
@@ -18,18 +17,18 @@
(defn- share-action
[address share-title]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content address}
:item {:default {:type "text"
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})))
(rf/dispatch [:open-share
{:options (if platform/ios?
{:activityItemSources [{:placeholderItem {:type :text
:content address}
:item {:default {:type :text
:content
address}}
:linkMetadata {:title share-title}}]}
{:title share-title
:subject share-title
:message address
:isNewTask true})}]))
(defn- open-preferences
[selected-networks]
@@ -15,6 +15,7 @@
:button-one-label (i18n/label :t/confirm-bridge)
:button-one-props {:icon-left :i/bridge}
:on-navigate-back (fn []
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:navigate-back]))}]])
(def view (quo.theme/with-theme view-internal))
@@ -0,0 +1,18 @@
(ns status-im.contexts.wallet.common.utils.networks
(:require [clojure.string :as string]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]))
(defn resolve-receiver-networks
[{:keys [prefix testnet-enabled? goerli-enabled?]}]
(let [prefix (if (string/blank? prefix)
constants/default-multichain-address-prefix
prefix)
prefix-seq (string/split prefix #":")]
(->> prefix-seq
(remove string/blank?)
(mapv
#(utils/network->chain-id
{:network %
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})))))
@@ -1,5 +1,6 @@
(ns status-im.contexts.wallet.common.utils.send
(:require [utils.money :as money]))
(:require [clojure.string :as string]
[utils.money :as money]))
(defn calculate-gas-fee
[data]
@@ -20,3 +21,17 @@
(defn calculate-full-route-gas-fee
[route]
(reduce money/add (map calculate-gas-fee route)))
(defn find-affordable-networks
[{:keys [balances-per-chain input-value selected-networks disabled-chain-ids]}]
(let [input-value (if (string/blank? input-value) 0 input-value)]
(->> balances-per-chain
(filter (fn [[_
{:keys [balance chain-id]
:or {balance 0}}]]
(and
(money/greater-than-or-equals (money/bignumber balance)
(money/bignumber input-value))
(some #(= % chain-id) selected-networks)
(not-any? #(= % chain-id) disabled-chain-ids))))
(map first))))
@@ -20,3 +20,44 @@
expected-eip1559-disabled-result (money/bignumber 0.000760406)]
(is (money/equal-to (utils/calculate-gas-fee data-eip1559-disabled)
expected-eip1559-disabled-result))))))
(deftest test-find-affordable-networks
(testing "All networks affordable and selected, none disabled"
(let [balances-per-chain {"1" {:balance "50.0" :chain-id "1"}
"2" {:balance "40.0" :chain-id "2"}}
input-value 20
selected-networks ["1" "2"]
disabled-chain-ids []
expected ["1" "2"]]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected)))))
(testing "No networks affordable"
(let [balances-per-chain {"1" {:balance "5.0" :chain-id "1"}
"2" {:balance "1.0" :chain-id "2"}}
input-value 10
selected-networks ["1" "2"]
disabled-chain-ids []
expected []]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected)))))
(testing "Selected networks subset, with some disabled"
(let [balances-per-chain {"1" {:balance "100.0" :chain-id "1"}
"2" {:balance "50.0" :chain-id "2"}
"3" {:balance "20.0" :chain-id "3"}}
input-value 15
selected-networks ["1" "2" "3"]
disabled-chain-ids ["2"]
expected ["1" "3"]]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected))))))
+1 -6
View File
@@ -2,12 +2,7 @@
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as rf]
[react-native.share :as share]))
(rf/reg-fx :effects.share/open
(fn [content]
(share/open content)))
[re-frame.core :as rf]))
(rf/reg-fx
:effects.wallet/create-account-from-mnemonic
+10 -10
View File
@@ -402,16 +402,16 @@
(rf/reg-event-fx :wallet/share-account
(fn [_ [{:keys [content title]}]]
{:fx [[:effects.share/open
(if platform/ios?
{:activityItemSources
[{:placeholderItem {:type "text"
:content content}
:item {:default {:type "text"
:content content}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message content})]]}))
{:options (if platform/ios?
{:activityItemSources
[{:placeholderItem {:type :text
:content content}
:item {:default {:type :text
:content content}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message content})}]]}))
(rf/reg-event-fx
:wallet/blockchain-status-changed
@@ -23,5 +23,5 @@
(defn home-container
[]
{:margin-top (safe-area/get-top)
{:margin-top (+ (safe-area/get-top) 8)
:flex 1})
+77 -27
View File
@@ -5,6 +5,7 @@
[native-module.core :as native-module]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[status-im.contexts.wallet.send.utils :as send-utils]
[taoensso.timbre :as log]
[utils.address :as address]
@@ -24,45 +25,71 @@
(rf/reg-event-fx :wallet/suggested-routes-success
(fn [{:keys [db]} [suggested-routes timestamp]]
(when (= (get-in db [:wallet :ui :send :suggested-routes-call-timestamp]) timestamp)
(let [suggested-routes-data (cske/transform-keys transforms/->kebab-case-keyword suggested-routes)
chosen-route (:best suggested-routes-data)]
(let [suggested-routes-data (cske/transform-keys transforms/->kebab-case-keyword
suggested-routes)
chosen-route (:best suggested-routes-data)
token (get-in db [:wallet :ui :send :token])
collectible (get-in db [:wallet :ui :send :collectible])
token-display-name (get-in db [:wallet :ui :send :token-display-name])
token-decimals (if collectible 0 (:decimals token))
native-token? (and token (= token-display-name "ETH"))
from-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route
:token-decimals
token-decimals
:native-token?
native-token?
:to? false})
from-network-values-for-ui (send-utils/network-values-for-ui from-network-amounts-by-chain)
to-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route
:token-decimals
token-decimals
:native-token?
native-token?
:to? true})
to-network-values-for-ui (send-utils/network-values-for-ui to-network-amounts-by-chain)]
{:db (-> db
(assoc-in [:wallet :ui :send :suggested-routes] suggested-routes-data)
(assoc-in [:wallet :ui :send :route] chosen-route)
(assoc-in [:wallet :ui :send :from-values-by-chain] from-network-values-for-ui)
(assoc-in [:wallet :ui :send :to-values-by-chain] to-network-values-for-ui)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))))
(rf/reg-event-fx :wallet/suggested-routes-error
(fn [{:keys [db]} [_error]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(update-in [:wallet :ui :send] dissoc :suggested-routes :route)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))
(rf/reg-event-fx :wallet/clean-suggested-routes
(fn [{:keys [db]}]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(update-in [:wallet :ui :send] dissoc :loading-suggested-routes?))}))
{:db (update-in db
[:wallet :ui :send]
dissoc
:suggested-routes
:route
:from-values-by-chain
:to-values-by-chain
:loading-suggested-routes?
:suggested-routes-call-timestamp)}))
(rf/reg-event-fx :wallet/clean-send-address
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :recipient :to-address)}))
(rf/reg-event-fx :wallet/clean-disabled-from-networks
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :disabled-from-chain-ids)}))
(rf/reg-event-fx
:wallet/select-send-address
(fn [{:keys [db]} [{:keys [address recipient stack-id start-flow?]}]]
(let [[prefix to-address] (utils/split-prefix-and-address address)
testnet-enabled? (get-in db [:profile/profile :test-networks-enabled?])
goerli-enabled? (get-in db [:profile/profile :is-goerli-enabled?])
prefix-seq (string/split prefix #":")
selected-networks (->> prefix-seq
(remove string/blank?)
(mapv
#(utils/network->chain-id
{:network %
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})))]
selected-networks (network-utils/resolve-receiver-networks
{:prefix prefix
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})]
{:db (-> db
(assoc-in [:wallet :ui :send :recipient] (or recipient address))
(assoc-in [:wallet :ui :send :to-address] to-address)
@@ -84,7 +111,8 @@
(fn [{:keys [db]} [{:keys [token stack-id start-flow?]}]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :collectible)
(assoc-in [:wallet :ui :send :token] token))
(assoc-in [:wallet :ui :send :token] token)
(assoc-in [:wallet :ui :send :token-display-name] (:symbol token)))
:fx [[:dispatch [:wallet/clean-suggested-routes]]
[:dispatch
[:wallet/wizard-navigate-forward
@@ -95,13 +123,15 @@
(rf/reg-event-fx
:wallet/edit-token-to-send
(fn [{:keys [db]} [token]]
{:db (assoc-in db [:wallet :ui :send :token] token)
{:db (-> db
(assoc-in [:wallet :ui :send :token] token)
(assoc-in [:wallet :ui :send :token-display-name] token))
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:wallet/clean-suggested-routes]]]}))
(rf/reg-event-fx :wallet/clean-selected-token
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :token :tx-type)}))
{:db (update-in db [:wallet :ui :send] dissoc :token :token-display-name :tx-type)}))
(rf/reg-event-fx :wallet/clean-selected-collectible
(fn [{:keys [db]}]
@@ -110,18 +140,30 @@
[:wallet :ui :send]
dissoc
:collectible
:token-display-name
:amount
(when (= transaction-type :collecible) :tx-type))})))
(rf/reg-event-fx :wallet/send-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id amount]}]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :token)
(assoc-in [:wallet :ui :send :collectible] collectible)
(assoc-in [:wallet :ui :send :tx-type] :collectible)
(assoc-in [:wallet :ui :send :amount] amount))
:fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]
[:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]}))
(let [collection-data (:collection-data collectible)
collectible-data (:collectible-data collectible)
collectible-id (get-in collectible [:id :token-id])
token-display-name (cond
(and collectible
(not (string/blank? (:name collectible-data))))
(:name collectible-data)
collectible
(str (:name collection-data) " #" collectible-id))]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :token)
(assoc-in [:wallet :ui :send :collectible] collectible)
(assoc-in [:wallet :ui :send :token-display-name] token-display-name)
(assoc-in [:wallet :ui :send :tx-type] :collectible)
(assoc-in [:wallet :ui :send :amount] amount))
:fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]
[:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]})))
(rf/reg-event-fx :wallet/select-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id]}]]
@@ -140,6 +182,12 @@
:start-flow? start-flow?
:flow-id :wallet-flow}]]]}))
(rf/reg-event-fx :wallet/disable-from-networks
(fn [{:keys [db]} [chain-ids]]
{:db (-> db
(assoc-in [:wallet :ui :send :disabled-from-chain-ids] chain-ids)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] true))}))
(rf/reg-event-fx :wallet/get-suggested-routes
(fn [{:keys [db now]} [{:keys [amount]}]]
(let [wallet-address (get-in db [:wallet :current-viewing-account-address])
@@ -147,6 +195,7 @@
transaction-type (get-in db [:wallet :ui :send :tx-type])
collectible (get-in db [:wallet :ui :send :collectible])
to-address (get-in db [:wallet :ui :send :to-address])
disabled-from-chain-ids (or (get-in db [:wallet :ui :send :disabled-from-chain-ids]) [])
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
networks ((if test-networks-enabled? :test :prod)
(get-in db [:wallet :networks]))
@@ -163,7 +212,7 @@
gas-rates constants/gas-rate-medium
amount-in (send-utils/amount-in-hex amount (if token token-decimal 0))
from-address wallet-address
disabled-from-chain-ids []
disabled-from-chain-ids disabled-from-chain-ids
disabled-to-chain-ids (if (= transaction-type :bridge)
(filter #(not= % bridge-to-chain-id) network-chain-ids)
[])
@@ -218,6 +267,7 @@
{:fx [[:dispatch [:wallet/clean-scanned-address]]
[:dispatch [:wallet/clean-local-suggestions]]
[:dispatch [:wallet/clean-send-address]]
[:dispatch [:wallet/clean-disabled-from-networks]]
[:dispatch [:wallet/select-address-tab nil]]
[:dispatch [:dismiss-modal :screen/wallet.transaction-progress]]]}))
@@ -5,60 +5,75 @@
[status-im.contexts.wallet.send.input-amount.view :as input-amount]
[test-helpers.component :as h]
[utils.debounce :as debounce]
[utils.money :as money]
[utils.re-frame :as rf]))
(set! rf/dispatch #())
(set! debounce/debounce-and-dispatch #())
(def sub-mocks
{:profile/profile {:currency :usd}
:wallet/selected-network-details [{:source 525
:short-name "eth"
:network-name :mainnet
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:mainnet :arbitrum
:optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:wallet/wallet-send-token {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route [{:from {:chainid 1
:native-currency-symbol "ETH"}
:to {:chain-id 1
:native-currency-symbol "ETH"}
:gas-amount "23487"
:gas-fees {:base-fee "32.325296406"
:max-priority-fee-per-gas "0.011000001"
:eip1559-enabled true}}]
:wallet/wallet-send-suggested-routes {:candidates []}
:wallet/wallet-send-selected-networks []
:view-id :screen/wallet.send-input-amount
:wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064"
:profile/currency-symbol "$"
:wallet/token-by-symbol {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}})
{:profile/profile {:currency :usd}
:wallet/selected-network-details [{:source 525
:short-name "eth"
:network-name :mainnet
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:mainnet :arbitrum
:optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:wallet/wallet-send-token {:symbol :eth
:networks [{:source 879
:short-name "eth"
:network-name :mainnet
:abbreviated-name "Eth."
:chain-id 1
:related-chain-id 1
:layer 1}]}
:wallet/current-viewing-account-tokens-filtered {:balances-per-chain {1 {:raw-balance
(money/bignumber
"2500")
:has-error false}}
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route [{:from {:chainid 1
:native-currency-symbol "ETH"}
:to {:chain-id 1
:native-currency-symbol "ETH"}
:gas-amount "23487"
:gas-fees {:base-fee "32.325296406"
:max-priority-fee-per-gas "0.011000001"
:eip1559-enabled true}}]
:wallet/wallet-send-suggested-routes {:candidates []}
:wallet/wallet-send-selected-networks [1]
:view-id :screen/wallet.send-input-amount
:wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064"
:profile/currency-symbol "$"
:wallet/token-by-symbol {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-disabled-from-chain-ids []
:wallet/wallet-send-from-values-by-chain {1 (money/bignumber "250")}
:wallet/wallet-send-to-values-by-chain {1 (money/bignumber "250")}})
(h/describe "Send > input amount screen"
(h/setup-restorable-re-frame)
@@ -6,6 +6,7 @@
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.common.controlled-input.utils :as controlled-input]
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
[status-im.contexts.wallet.common.asset-list.view :as asset-list]
[status-im.contexts.wallet.common.utils :as utils]
@@ -13,86 +14,17 @@
[status-im.contexts.wallet.send.input-amount.style :as style]
[status-im.contexts.wallet.send.routes.view :as routes]
[utils.address :as address]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- make-limit-label
[{:keys [amount currency]}]
[amount currency]
(str amount
" "
(some-> currency
name
string/upper-case)))
(def not-digits-or-dot-pattern
#"[^0-9+\.]")
(def dot ".")
(defn valid-input?
[current v]
(let [max-length 12
length-overflow? (>= (count current) max-length)
extra-dot? (and (= v dot) (string/includes? current dot))
extra-leading-zero? (and (= current "0") (= "0" (str v)))
non-numeric? (re-find not-digits-or-dot-pattern (str v))]
(not (or non-numeric? extra-dot? extra-leading-zero? length-overflow?))))
(defn- add-char-to-string
[s c idx]
(let [size (count s)]
(if (= size idx)
(str s c)
(str (subs s 0 idx)
c
(subs s idx size)))))
(defn- move-input-cursor
([input-selection-atom new-idx]
(move-input-cursor input-selection-atom new-idx new-idx))
([input-selection-atom new-start-idx new-end-idx]
(let [start-idx (if (< new-start-idx 0) 0 new-start-idx)
end-idx (if (< new-end-idx 0) 0 new-start-idx)]
(swap! input-selection-atom assoc :start start-idx :end end-idx))))
(defn- normalize-input
[current v input-selection-atom]
(let [{:keys [start end]} @input-selection-atom]
(if (= start end)
(cond
(and (string/blank? current) (= v dot))
(do
(move-input-cursor input-selection-atom 2)
(str "0" v))
(and (= current "0") (not= v dot))
(do
(move-input-cursor input-selection-atom 1)
(str v))
:else
(do
(move-input-cursor input-selection-atom (inc start))
(add-char-to-string current v start)))
current)))
(defn- make-new-input
[current v input-selection-atom]
(if (valid-input? current v)
(normalize-input current v input-selection-atom)
current))
(defn- reset-input-error
[new-value prev-value input-error]
(reset! input-error
(> new-value prev-value)))
(defn delete-from-string
[s idx]
(let [size (count s)]
(str (subs s 0 (dec idx)) (subs s idx size))))
(defn- estimated-fees
[{:keys [loading-suggested-routes? fees amount receiver]}]
[rn/view {:style style/estimated-fees-container}
@@ -149,165 +81,117 @@
initial-crypto-currency? :initial-crypto-currency?
:or {initial-crypto-currency? true}}]
(let [_ (rn/dismiss-keyboard!)
bottom (safe-area/get-bottom)
input-value (reagent/atom "")
clear-input! #(reset! input-value "")
input-error (reagent/atom false)
crypto-currency? (reagent/atom initial-crypto-currency?)
input-selection (reagent/atom {:start 0 :end 0})
handle-swap (fn [{:keys [crypto? limit-fiat limit-crypto]}]
(let [num-value (parse-double @input-value)
current-limit (if crypto? limit-crypto limit-fiat)]
(reset! crypto-currency? crypto?)
(reset-input-error num-value current-limit input-error)))
handle-keyboard-press (fn [v loading-routes? current-limit-amount]
(when-not loading-routes?
(let [current-value @input-value
new-value (make-new-input current-value v input-selection)
num-value (or (parse-double new-value) 0)]
(reset! input-value new-value)
(reset-input-error num-value current-limit-amount input-error)
(reagent/flush))))
handle-delete (fn [loading-routes? current-limit-amount]
(when-not loading-routes?
(let [{:keys [start end]} @input-selection
new-value (delete-from-string @input-value start)]
(when (= start end)
(reset-input-error new-value current-limit-amount input-error)
(swap! input-value delete-from-string start)
(move-input-cursor input-selection (dec start)))
(reagent/flush))))
on-long-press-delete (fn [loading-routes?]
(when-not loading-routes?
(reset! input-value "")
(reset! input-error false)
(move-input-cursor input-selection 0)
(reagent/flush)))
handle-on-change (fn [v current-limit-amount]
(when (valid-input? @input-value v)
(let [num-value (or (parse-double v) 0)]
(reset! input-value v)
(reset-input-error num-value current-limit-amount input-error)
(reagent/flush))))
on-navigate-back on-navigate-back
fetch-routes (fn [input-num-value current-limit-amount]
(let [nav-current-screen-id (rf/sub [:view-id])]
; this check is to prevent effect being triggered when screen is
; loaded but not being shown to the user (deep in the navigation
; stack) and avoid undesired behaviors
(when (= nav-current-screen-id current-screen-id)
(if-not (or (empty? @input-value)
(<= input-num-value 0)
(> input-num-value current-limit-amount))
(debounce/debounce-and-dispatch
[:wallet/get-suggested-routes {:amount @input-value}]
2000)
(rf/dispatch [:wallet/clean-suggested-routes])))))
handle-on-confirm (fn []
(rf/dispatch [:wallet/send-select-amount
{:amount @input-value
:stack-id current-screen-id}]))
selection-change (fn [selection]
;; `reagent/flush` is needed to properly propagate the
;; input cursor state. Since this is a controlled
;; component the cursor will become static if
;; `reagent/flush` is removed.
(reset! input-selection selection)
(reagent/flush))]
input-state (controlled-input/create-input-state)
bottom (safe-area/get-bottom)
clear-input! #(controlled-input/delete-all input-state)
crypto-currency? (reagent/atom initial-crypto-currency?)
on-navigate-back on-navigate-back
handle-on-confirm (fn []
(rf/dispatch [:wallet/send-select-amount
{:amount (controlled-input/input-value input-state)
:stack-id current-screen-id}]))]
(fn []
(let [{fiat-currency :currency} (rf/sub [:profile/profile])
{token-balance :total-balance
token-symbol :symbol
token-networks :networks
:as token} (rf/sub [:wallet/wallet-send-token])
conversion-rate (-> token :market-values-per-currency :usd :price)
loading-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
best-routes (when suggested-routes (or (:best suggested-routes) []))
route (rf/sub [:wallet/wallet-send-route])
to-address (rf/sub [:wallet/wallet-send-to-address])
on-confirm (or default-on-confirm handle-on-confirm)
crypto-decimals (or default-crypto-decimals
(utils/get-crypto-decimals-count token))
crypto-limit (or default-limit-crypto
(utils/get-standard-crypto-format token token-balance))
fiat-limit (.toFixed (* token-balance conversion-rate) 2)
current-limit #(if @crypto-currency? crypto-limit fiat-limit)
current-currency (if @crypto-currency? token-symbol fiat-currency)
limit-label (make-limit-label {:amount (current-limit)
:currency current-currency})
input-num-value (parse-double @input-value)
confirm-disabled? (or (nil? route)
(empty? route)
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (current-limit)))
amount-text (str @input-value " " token-symbol)
native-currency-symbol (when-not confirm-disabled?
(get-in route [:from :native-currency-symbol]))
native-token (when native-currency-symbol
(rf/sub [:wallet/token-by-symbol native-currency-symbol]))
fee-in-native-token (when-not confirm-disabled?
(send-utils/calculate-full-route-gas-fee route))
fee-in-crypto-formatted (when fee-in-native-token
(utils/get-standard-crypto-format native-token
fee-in-native-token))
fee-in-fiat (when-not confirm-disabled?
(utils/calculate-token-fiat-value
{:currency fiat-currency
:balance fee-in-native-token
:token native-token}))
currency-symbol (rf/sub [:profile/currency-symbol])
fee-formatted (when fee-in-fiat
(utils/get-standard-fiat-format fee-in-crypto-formatted
currency-symbol
fee-in-fiat))
show-select-asset-sheet #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[select-asset-bottom-sheet clear-input!])}])]
(let [{fiat-currency :currency} (rf/sub [:profile/profile])
{token-symbol :symbol
token-networks :networks} (rf/sub [:wallet/wallet-send-token])
{token-balance :total-balance
:as
token} (rf/sub
[:wallet/current-viewing-account-tokens-filtered
(str token-symbol)])
conversion-rate (-> token :market-values-per-currency :usd :price)
loading-routes? (rf/sub
[:wallet/wallet-send-loading-suggested-routes?])
route (rf/sub [:wallet/wallet-send-route])
to-address (rf/sub [:wallet/wallet-send-to-address])
nav-current-screen-id (rf/sub [:view-id])
on-confirm (or default-on-confirm handle-on-confirm)
crypto-decimals (or default-crypto-decimals
(utils/get-crypto-decimals-count token))
crypto-limit (or default-limit-crypto
(utils/get-standard-crypto-format
token
token-balance))
fiat-limit (.toFixed (* token-balance conversion-rate) 2)
current-limit #(if @crypto-currency? crypto-limit fiat-limit)
routes-can-be-fetched? (and (= nav-current-screen-id current-screen-id)
(not (or (empty? (controlled-input/input-value input-state))
(<= (controlled-input/numeric-value input-state) 0)
(> (controlled-input/numeric-value input-state)
(current-limit)))))
current-currency (if @crypto-currency? token-symbol fiat-currency)
input-num-value (controlled-input/numeric-value input-state)
confirm-disabled? (or (nil? route)
(empty? route)
(empty? (controlled-input/input-value input-state))
(<= input-num-value 0)
(> input-num-value (current-limit)))
amount-text (str (controlled-input/input-value input-state) " " token-symbol)
native-currency-symbol (when-not confirm-disabled?
(get-in route [:from :native-currency-symbol]))
native-token (when native-currency-symbol
(rf/sub [:wallet/token-by-symbol
native-currency-symbol]))
fee-in-native-token (when-not confirm-disabled?
(send-utils/calculate-full-route-gas-fee route))
fee-in-crypto-formatted (when fee-in-native-token
(utils/get-standard-crypto-format
native-token
fee-in-native-token))
fee-in-fiat (when-not confirm-disabled?
(utils/calculate-token-fiat-value
{:currency fiat-currency
:balance fee-in-native-token
:token native-token}))
currency-symbol (rf/sub [:profile/currency-symbol])
fee-formatted (when fee-in-fiat
(utils/get-standard-fiat-format
fee-in-crypto-formatted
currency-symbol
fee-in-fiat))
show-select-asset-sheet #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[select-asset-bottom-sheet
clear-input!])}])]
(rn/use-mount
(fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
#(.remove app-keyboard-listener))))
(rn/use-effect
#(fetch-routes input-num-value (current-limit))
[@input-value])
#(controlled-input/set-upper-limit input-state (current-limit))
[@crypto-currency?])
[rn/view
{:style style/screen
:accessibility-label (str "container" (when @input-error "-error"))}
:accessibility-label (str "container"
(when (controlled-input/input-error input-state) "-error"))}
[account-switcher/view
{:icon-name :i/arrow-left
:on-press on-navigate-back
:switcher-type :select-account}]
[quo/token-input
{:container-style style/input-container
:token token-symbol
:currency current-currency
:crypto-decimals crypto-decimals
:error? @input-error
:networks (seq token-networks)
:title (i18n/label :t/send-limit {:limit limit-label})
:conversion conversion-rate
:show-keyboard? false
:value @input-value
:selection @input-selection
:on-change-text #(handle-on-change % (current-limit))
:on-selection-change selection-change
:on-swap #(handle-swap
{:crypto? %
:currency current-currency
:token-symbol token-symbol
:limit-fiat fiat-limit
:limit-crypto crypto-limit})
:on-token-press show-select-asset-sheet}]
{:container-style style/input-container
:token token-symbol
:currency current-currency
:crypto-decimals crypto-decimals
:error? (controlled-input/input-error input-state)
:networks (seq token-networks)
:title (i18n/label :t/send-limit
{:limit (make-limit-label (current-limit) current-currency)})
:conversion conversion-rate
:show-keyboard? false
:value (controlled-input/input-value input-state)
:on-swap #(reset! crypto-currency? %)
:on-token-press show-select-asset-sheet}]
[routes/view
{:amount amount-text
:routes best-routes
:token token
:input-value @input-value
:fetch-routes #(fetch-routes % (current-limit))}]
{:token token
:input-value (controlled-input/input-value input-state)
:routes-can-be-fetched? routes-can-be-fetched?}]
(when (or loading-routes? (seq route))
[estimated-fees
{:loading-suggested-routes? loading-routes?
@@ -324,9 +208,15 @@
{:container-style (style/keyboard-container bottom)
:left-action :dot
:delete-key? true
:on-press #(handle-keyboard-press % loading-routes? (current-limit))
:on-delete #(handle-delete loading-routes? (current-limit))
:on-long-press-delete #(on-long-press-delete loading-routes?)}]]))))
:on-press (fn [c]
(when-not loading-routes?
(controlled-input/add-character input-state c)))
:on-delete (fn []
(when-not loading-routes?
(controlled-input/delete-last input-state)))
:on-long-press-delete (fn []
(when-not loading-routes?
(controlled-input/delete-all input-state)))}]]))))
(defn- view-internal
[props]
@@ -12,20 +12,23 @@
{:flex-direction :row
:justify-content :space-between})
(def routes-inner-container
{:margin-top 8
(defn routes-inner-container
[first-item?]
{:margin-top (if first-item? 7.5 11)
:flex-direction :row
:align-items :center
:justify-content :space-between})
(defn section-label
[margin-left]
{:flex 0.5
:margin-left margin-left})
(def section-label-right
{:width 135})
(def section-label-left
{:width 136})
(def network-link
{:right 6
:z-index 1})
{:margin-horizontal -1.5
:z-index 1
:flex 1})
(def empty-container
{:flex-grow 1
@@ -33,9 +36,8 @@
:justify-content :center})
(def add-network
{:margin-top 8
:align-self :flex-end
:right 12})
{:margin-top 11
:align-self :flex-end})
(defn warning-container
[color theme]
@@ -7,19 +7,19 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.send :as send-utils]
[status-im.contexts.wallet.send.routes.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.vector :as vector-utils]))
(defn- find-affordable-networks
[{:keys [balances-per-chain]} input-value selected-networks]
(->> balances-per-chain
(filter (fn [[_ {:keys [balance chain-id]}]]
(and
(>= (js/parseFloat balance) input-value)
(some #(= % chain-id) selected-networks))))
(map first)))
(def ^:private network-priority-score
{:ethereum 1
:optimism 2
:arbitrum 3})
(defn- make-network-item
[{:keys [network-name chain-id] :as _network}
@@ -34,6 +34,36 @@
:checked? (some #(= % chain-id) @network-preferences)
:on-change on-change}})
(defn- find-network-link-insertion-index
[network-links chain-id loading-suggested-routes?]
(let [network (utils/id->network chain-id)
inserted-network-link-priority-score (network-priority-score network)]
(or (->> network-links
(keep-indexed (fn [idx network-link]
(let [network-link (utils/id->network (if loading-suggested-routes?
network-link
(get-in network-link
[:from :chain-id])))]
(when (> (network-priority-score network-link)
inserted-network-link-priority-score)
idx))))
first)
(count network-links))))
(defn- add-disabled-networks
[network-links disabled-from-networks loading-suggested-routes?]
(let [sorted-networks (sort-by (comp network-priority-score utils/id->network) disabled-from-networks)]
(reduce (fn [acc-network-links chain-id]
(let [index (find-network-link-insertion-index acc-network-links
chain-id
loading-suggested-routes?)
disabled-network-link {:status :disabled
:chain-id chain-id
:network (utils/id->network chain-id)}]
(vector-utils/insert-element-at acc-network-links disabled-network-link index)))
network-links
sorted-networks)))
(defn networks-drawer
[{:keys [fetch-routes theme]}]
(let [network-details (rf/sub [:wallet/network-details])
@@ -90,7 +120,9 @@
:customization-color color}}]])))
(defn route-item
[{:keys [amount from-network to-network status theme fetch-routes]}]
[{:keys [first-item? from-amount to-amount token-symbol from-chain-id to-chain-id from-network
to-network on-press-from-network on-press-to-network status theme fetch-routes disabled?
loading?]}]
(if (= status :add)
[quo/network-bridge
{:status :add
@@ -99,59 +131,170 @@
{:content (fn [] [networks-drawer
{:theme theme
:fetch-routes fetch-routes}])}])}]
[rn/view {:style style/routes-inner-container}
[rn/view {:style (style/routes-inner-container first-item?)}
[quo/network-bridge
{:amount amount
:network from-network
:status status}]
{:amount (str from-amount " " token-symbol)
:network from-network
:status status
:on-press #(when (and on-press-from-network (not loading?))
(on-press-from-network from-chain-id from-amount))}]
(if (= status :default)
[quo/network-link
{:shape :linear
:source from-network
:destination to-network
:container-style style/network-link}]
[rn/view {:style {:width 73}}])
[rn/view {:style {:flex 1}}])
[quo/network-bridge
{:amount amount
:network to-network
:status status
:container-style {:right 12}}]]))
{:amount (str to-amount " " token-symbol)
:network to-network
:status (cond
(and disabled? loading?) :loading
(and disabled? (not loading?)) :default
:else status)
:on-press #(when (and on-press-to-network (not loading?))
(on-press-to-network to-chain-id to-amount))}]]))
(defn- render-network-link
[item index _
{:keys [from-values-by-chain to-values-by-chain theme fetch-routes on-press-from-network
on-press-to-network token-symbol loading-suggested-routes?]}]
(let [first-item? (zero? index)
disabled-network? (= (:status item) :disabled)
from-chain-id (get-in item [:from :chain-id])
to-chain-id (get-in item [:to :chain-id])
from-amount (when from-chain-id
(from-values-by-chain from-chain-id))
to-amount (when to-chain-id
(to-values-by-chain to-chain-id))]
[route-item
{:first-item? first-item?
:from-amount (if disabled-network? 0 from-amount)
:to-amount (if disabled-network? 0 to-amount)
:token-symbol token-symbol
:disabled? disabled-network?
:loading? loading-suggested-routes?
:theme theme
:fetch-routes fetch-routes
:status (cond
(= (:status item) :add) :add
(= (:status item) :disabled) :disabled
loading-suggested-routes? :loading
:else :default)
:from-chain-id (or from-chain-id (:chain-id item))
:to-chain-id (or to-chain-id (:chain-id item))
:from-network (cond (and loading-suggested-routes?
(not disabled-network?))
(utils/id->network item)
disabled-network?
(utils/id->network (:chain-id
item))
:else
(utils/id->network from-chain-id))
:to-network (cond (and loading-suggested-routes?
(not disabled-network?))
(utils/id->network item)
disabled-network?
(utils/id->network (:chain-id
item))
:else
(utils/id->network to-chain-id))
:on-press-from-network on-press-from-network
:on-press-to-network on-press-to-network}]))
(defn fetch-routes
[amount routes-can-be-fetched? bounce-duration-ms]
(if routes-can-be-fetched?
(debounce/debounce-and-dispatch
[:wallet/get-suggested-routes {:amount amount}]
bounce-duration-ms)
(rf/dispatch [:wallet/clean-suggested-routes])))
(defn- view-internal
[{:keys [amount routes token input-value theme fetch-routes]}]
(let [selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
loading-networks (find-affordable-networks token input-value selected-networks)
loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
data (if loading-suggested-routes? loading-networks routes)]
(if (or (and (not-empty loading-networks) loading-suggested-routes?) (not-empty routes))
[rn/flat-list
{:data (if (and (< (count data) 3) (pos? (count data)))
(concat data [{:status :add}])
data)
:content-container-style style/routes-container
:header [rn/view {:style style/routes-header-container}
[quo/section-label
{:section (i18n/label :t/from-label)
:container-style (style/section-label 0)}]
[quo/section-label
{:section (i18n/label :t/to-label)
:container-style (style/section-label 64)}]]
:render-fn (fn [item]
[route-item
{:amount amount
:theme theme
:fetch-routes fetch-routes
:status (cond
(= (:status item) :add) :add
loading-suggested-routes? :loading
:else :default)
:from-network (if loading-suggested-routes?
(utils/id->network item)
(utils/id->network (get-in item [:from :chain-id])))
:to-network (if loading-suggested-routes?
(utils/id->network item)
(utils/id->network (get-in item
[:to :chain-id])))}])}]
[{:keys [token theme input-value routes-can-be-fetched?
on-press-to-network]}]
(let [token-symbol (:symbol token)
loading-suggested-routes? (rf/sub
[:wallet/wallet-send-loading-suggested-routes?])
from-values-by-chain (rf/sub
[:wallet/wallet-send-from-values-by-chain])
to-values-by-chain (rf/sub [:wallet/wallet-send-to-values-by-chain])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
disabled-from-chain-ids (rf/sub
[:wallet/wallet-send-disabled-from-chain-ids])
routes (when suggested-routes
(or (:best suggested-routes) []))
{token-balances-per-chain :balances-per-chain} (rf/sub
[:wallet/current-viewing-account-tokens-filtered
(str token-symbol)])
affordable-networks (send-utils/find-affordable-networks
{:balances-per-chain token-balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-from-chain-ids})
network-links (if loading-suggested-routes?
affordable-networks
routes)
show-routes? (or (and (not-empty affordable-networks)
loading-suggested-routes?)
(not-empty routes))]
(rn/use-effect
#(when (> (count affordable-networks) 0)
(fetch-routes input-value routes-can-be-fetched? 2000))
[input-value routes-can-be-fetched?])
(rn/use-effect
#(when (> (count affordable-networks) 0)
(fetch-routes input-value routes-can-be-fetched? 0))
[disabled-from-chain-ids])
(if show-routes?
(let [initial-network-links-count (count network-links)
disabled-count (count disabled-from-chain-ids)
network-links (if (not-empty disabled-from-chain-ids)
(add-disabled-networks network-links
disabled-from-chain-ids
loading-suggested-routes?)
network-links)
network-links-with-add-button (if (and (< (- (count network-links) disabled-count)
constants/default-network-count)
(pos? initial-network-links-count))
(concat network-links [{:status :add}])
network-links)]
[rn/flat-list
{:data network-links-with-add-button
:content-container-style style/routes-container
:header [rn/view {:style style/routes-header-container}
[quo/section-label
{:section (i18n/label :t/from-label)
:container-style style/section-label-left}]
[quo/section-label
{:section (i18n/label :t/to-label)
:container-style style/section-label-right}]]
:render-data
{:from-values-by-chain from-values-by-chain
:to-values-by-chain to-values-by-chain
:theme theme
:fetch-routes #(fetch-routes % routes-can-be-fetched? 2000)
:on-press-from-network (fn [chain-id _]
(let [disabled-chain-ids (if (contains? (set
disabled-from-chain-ids)
chain-id)
(vec (remove #(= % chain-id)
disabled-from-chain-ids))
(conj disabled-from-chain-ids
chain-id))
re-enabling-chain? (< (count disabled-chain-ids)
(count disabled-from-chain-ids))]
(when (or re-enabling-chain?
(> (count affordable-networks) 1))
(rf/dispatch [:wallet/disable-from-networks
disabled-chain-ids]))))
:on-press-to-network on-press-to-network
:token-symbol token-symbol
:loading-suggested-routes? loading-suggested-routes?}
:render-fn render-network-link}])
[rn/view {:style style/empty-container}
(when (and (not (nil? routes)) (not loading-suggested-routes?))
[quo/text (i18n/label :t/no-routes-found)])])))
@@ -132,6 +132,7 @@
(rf/dispatch [:wallet/clean-selected-token])
(rf/dispatch [:wallet/clean-selected-collectible])
(rf/dispatch [:wallet/clean-send-address])
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:wallet/select-address-tab nil])
(rf/dispatch [:navigate-back]))
on-change-tab #(rf/dispatch [:wallet/select-address-tab %])
@@ -10,6 +10,8 @@
[input-amount/view
{:current-screen-id :screen/wallet.send-input-amount
:button-one-label (i18n/label :t/confirm)
:on-navigate-back #(rf/dispatch [:navigate-back])}])
:on-navigate-back (fn []
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:navigate-back]))}])
(def view (quo.theme/with-theme view-internal))
@@ -1,23 +1,21 @@
(ns status-im.contexts.wallet.send.transaction-confirmation.view
(:require
[clojure.string :as string]
[legacy.status-im.utils.hex :as utils.hex]
[legacy.status-im.utils.utils :as utils]
[native-module.core :as native-module]
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.floating-button-page.view :as floating-button-page]
[status-im.common.standard-authentication.core :as standard-auth]
[status-im.contexts.wallet.common.utils :as wallet-utils]
[status-im.contexts.wallet.send.transaction-confirmation.style :as style]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn- transaction-title
[{:keys [token-symbol amount account to-address route to-network image-url transaction-type
[{:keys [token-display-name amount account to-address route to-network image-url transaction-type
collectible?]}]
(let [to-network-name (:network-name to-network)
to-network-color (if (= to-network-name :mainnet) :ethereum to-network-name)]
@@ -32,8 +30,8 @@
(i18n/label :t/bridge)
(i18n/label :t/send))]
[quo/summary-tag
{:token (if collectible? "" token-symbol)
:label (str amount " " token-symbol)
{:token (if collectible? "" token-display-name)
:label (str amount " " token-display-name)
:type (if collectible? :collectible :token)
:image-source (if collectible? image-url :eth)}]]
(if (= transaction-type :bridge)
@@ -125,84 +123,34 @@
:emoji (:emoji account)
:customization-color (:color account)}]])]))
(defn network-name-from-chain-id
[chain-id]
(let [network-name (-> (rf/sub [:wallet/network-details-by-chain-id chain-id])
:network-name)]
(if (= network-name :mainnet) :ethereum network-name)))
(defn- network-amounts-from-route
[{:keys [route token-symbol token-decimals to?]}]
(reduce (fn [acc path]
(let [network (if to? (:to path) (:from path))
chain-id (:chain-id network)
amount-hex (if to? (:amount-in path) (:amount-out path))
amount-units (native-module/hex-to-number
(utils.hex/normalize-hex amount-hex))
amount (money/with-precision
(if (= token-symbol "ETH")
(money/wei->ether amount-units)
(money/token->unit amount-units
token-decimals))
6)
network-name (network-name-from-chain-id chain-id)]
(merge-with money/add acc {network-name amount})))
{}
route))
(defn- network-values-from-amounts
[network-amounts token-symbol]
(reduce-kv (fn [acc k v]
(assoc acc
k
{:amount v
:token-symbol token-symbol}))
{}
network-amounts))
(defn- sanitize-network-values
[network-values]
(into {}
(map (fn [[k v]]
[k
(if (money/equal-to (v :amount) 0)
(assoc v :amount "<0.01")
v)])
network-values)))
(defn- values-by-network
[{:keys [collectible amount token-symbol route token-decimals to?]}]
(if collectible
(let [collectible-chain-id (get-in collectible [:id :contract-id :chain-id])
network-name (network-name-from-chain-id collectible-chain-id)]
{network-name {:amount amount :token-symbol token-symbol}})
(let [network-amounts (network-amounts-from-route {:route route
:token-symbol token-symbol
:token-decimals token-decimals
:to? to?})
network-values (network-values-from-amounts network-amounts token-symbol)]
(sanitize-network-values network-values))))
(defn- user-summary
[{:keys [account-props theme label accessibility-label
summary-type network-values]
:as _props}]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label accessibility-label}
label]
[quo/summary-info
{:type summary-type
:networks? true
:values network-values
:account-props account-props}]])
[{:keys [network-values token-display-name account-props theme label accessibility-label
summary-type]}]
(let [network-values
(reduce-kv
(fn [acc chain-id amount]
(let [network-name (wallet-utils/id->network chain-id)]
(assoc acc
(if (= network-name :mainnet) :ethereum network-name)
{:amount amount :token-symbol token-display-name})))
{}
network-values)]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label accessibility-label}
label]
[quo/summary-info
{:type summary-type
:networks? true
:values network-values
:account-props account-props}]]))
(defn data-item
(defn- data-item
[{:keys [title subtitle]}]
[quo/data-item
{:container-style style/detail-item
@@ -217,7 +165,8 @@
:subtitle subtitle}])
(defn- transaction-details
[{:keys [estimated-time-min max-fees token-symbol amount to-address to-network route transaction-type
[{:keys [estimated-time-min max-fees token-display-name amount to-address to-network route
transaction-type
theme]}]
(let [currency-symbol (rf/sub [:profile/currency-symbol])
route-loaded? (and route (seq route))
@@ -253,56 +202,42 @@
(i18n/label :t/bridged-to
{:network (:abbreviated-name to-network)})
(i18n/label :t/user-gets {:name (utils/get-shortened-address to-address)}))
:subtitle (str amount " " token-symbol)}]]
:subtitle (str amount " " token-display-name)}]]
:else
[quo/text {:style {:align-self :center}}
(i18n/label :t/no-routes-found-confirmation)])]]))
(defn collectible-token-symbol
[collectible]
(let [collection-data (:collection-data collectible)
collectible-data (:collectible-data collectible)
collectible-id (get-in collectible [:id :token-id])]
(first (remove
string/blank?
[(:name collectible-data)
(str (:name collection-data) " #" collectible-id)]))))
(defn- view-internal
[_]
(let [on-close (fn []
(rf/dispatch [:wallet/clean-suggested-routes])
(rf/dispatch [:navigate-back]))]
(fn [{:keys [theme]}]
(let [send-transaction-data (rf/sub [:wallet/wallet-send])
{:keys [token collectible amount route
to-address bridge-to-chain-id]} send-transaction-data
collectible? (some? collectible)
token-symbol (if collectible
(collectible-token-symbol collectible)
(:symbol token))
token-decimals (if collectible 0 (:decimals token))
image-url (when collectible
(get-in collectible [:preview-url :uri]))
transaction-type (:tx-type send-transaction-data)
estimated-time-min (reduce + (map :estimated-time route))
max-fees "-"
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
bridge-to-network (when bridge-to-chain-id
(rf/sub [:wallet/network-details-by-chain-id
bridge-to-chain-id]))
from-account-props {:customization-color account-color
:size 32
:emoji (:emoji account)
:type :default
:name (:name account)
:address (utils/get-shortened-address
(:address
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address
to-address)}]
(let [send-transaction-data (rf/sub [:wallet/wallet-send])
{:keys [token-display-name collectible amount route
to-address bridge-to-chain-id
from-values-by-chain
to-values-by-chain]} send-transaction-data
collectible? (some? collectible)
image-url (when collectible
(get-in collectible [:preview-url :uri]))
transaction-type (:tx-type send-transaction-data)
estimated-time-min (reduce + (map :estimated-time route))
max-fees "-"
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
bridge-to-network (when bridge-to-chain-id
(rf/sub [:wallet/network-details-by-chain-id
bridge-to-chain-id]))
from-account-props {:customization-color account-color
:size 32
:emoji (:emoji account)
:type :default
:name (:name account)
:address (utils/get-shortened-address (:address
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address to-address)}]
[rn/view {:style {:flex 1}}
[floating-button-page/view
{:footer-container-padding 0
@@ -329,29 +264,26 @@
:customization-color (:color account)}
[rn/view
[transaction-title
{:token-symbol token-symbol
:amount amount
:account account
:to-address to-address
:route route
:to-network bridge-to-network
:image-url image-url
:transaction-type transaction-type
:collectible? collectible?}]
{:token-display-name token-display-name
:amount amount
:account account
:to-address to-address
:route route
:to-network bridge-to-network
:image-url image-url
:transaction-type transaction-type
:collectible? collectible?}]
[user-summary
{:summary-type :status-account
{:token-display-name token-display-name
:summary-type :status-account
:accessibility-label :summary-from-label
:label (i18n/label :t/from-capitalized)
:network-values from-values-by-chain
:account-props from-account-props
:theme theme
:network-values (values-by-network {:collectible collectible
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? false})}]
:theme theme}]
[user-summary
{:summary-type (if (= transaction-type :bridge)
{:token-display-name token-display-name
:summary-type (if (= transaction-type :bridge)
:status-account
:account)
:accessibility-label :summary-to-label
@@ -359,17 +291,12 @@
:account-props (if (= transaction-type :bridge)
from-account-props
user-props)
:theme theme
:network-values (values-by-network {:collectible collectible
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? true})}]
:network-values to-values-by-chain
:theme theme}]
[transaction-details
{:estimated-time-min estimated-time-min
:max-fees max-fees
:token-symbol token-symbol
:token-display-name token-display-name
:amount amount
:to-address to-address
:to-network bridge-to-network
+28 -1
View File
@@ -1,5 +1,8 @@
(ns status-im.contexts.wallet.send.utils
(:require [utils.money :as money]))
(:require
[legacy.status-im.utils.hex :as utils.hex]
[native-module.core :as native-module]
[utils.money :as money]))
(defn amount-in-hex
[amount token-decimal]
@@ -20,3 +23,27 @@
value1)))
{}
transaction-hashes))
(defn network-amounts-by-chain
[{:keys [route token-decimals native-token? to?]}]
(reduce (fn [acc path]
(let [amount-hex (if to? (:amount-in path) (:amount-out path))
amount-units (native-module/hex-to-number
(utils.hex/normalize-hex amount-hex))
amount (money/with-precision
(if native-token?
(money/wei->ether amount-units)
(money/token->unit amount-units
token-decimals))
6)
chain-id (if to? (get-in path [:to :chain-id]) (get-in path [:from :chain-id]))]
(update acc chain-id money/add amount)))
{}
route))
(defn network-values-for-ui
[amounts]
(reduce-kv (fn [acc k v]
(assoc acc k (if (money/equal-to v 0) "<0.01" v)))
{}
amounts))
@@ -1,6 +1,7 @@
(ns status-im.contexts.wallet.send.utils-test
(:require [cljs.test :refer [deftest is testing]]
[status-im.contexts.wallet.send.utils :as utils]))
[status-im.contexts.wallet.send.utils :as utils]
[utils.money :as money]))
(deftest test-amount-in-hex
(testing "Test amount-in-hex function"
@@ -27,3 +28,68 @@
"0x11" {:status :pending
:id 61
:chain-id :420}})))))
(deftest test-network-amounts-by-chain
(testing "Correctly calculates network amounts for transaction with native token"
(let [route [{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}
{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "2"}}]
token-decimals 18
native-token? true
to? true
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "1")
"2" (money/bignumber "1")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value)))))
(testing
"Correctly calculates network amounts for transaction with native token and multiple routes to same chain-id"
(let [route [{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}
{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}]
token-decimals 18
native-token? true
to? true
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "2")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value)))))
(testing "Correctly calculates network amounts for transaction with non-native token"
(let [route [{:amount-out "0x1e8480"
:from {:chain-id "1"}}
{:amount-out "0x1e8480"
:from {:chain-id "2"}}]
token-decimals 6
native-token? false
to? false
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "2")
"2" (money/bignumber "2")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value))))))
(deftest test-network-values-for-ui
(testing "Sanitizes values correctly for display"
(let [amounts {"1" (money/bignumber "0")
"2" (money/bignumber "2.5")
"3" (money/bignumber "0.005")}
result (utils/network-values-for-ui amounts)
expected {"1" "<0.01"
"2" (money/bignumber "2.5")
"3" (money/bignumber "0.005")}]
(doseq [[chain-id exp-value] expected]
(is #(or (= (get result chain-id) exp-value)
(money/equal-to (get result chain-id) exp-value)))))))
+2
View File
@@ -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
@@ -38,6 +39,7 @@
status-im.contexts.wallet.send.events
status-im.contexts.wallet.signals
[status-im.db :as db]
status-im.navigation.effects
status-im.navigation.events
[utils.re-frame :as rf]))
+14
View File
@@ -5,6 +5,7 @@
[react-native.core :as rn]
[react-native.navigation :as navigation]
[react-native.platform :as platform]
[react-native.share :as share]
[status-im.contexts.shell.jump-to.constants :as shell.constants]
[status-im.contexts.shell.jump-to.utils :as jump-to.utils]
[status-im.navigation.options :as options]
@@ -183,6 +184,19 @@
(rf/reg-fx :open-modal-fx open-modal)
;;;; Share
(rf/reg-fx :effects.share/open
(fn [{:keys [options on-success on-error]}]
(cond-> (share/open options)
(fn? on-success) (.then on-success)
:always (.catch (fn [error]
(log/error "Failed to share content"
{:error error
:effect :effects.share/open})
(when (fn? on-error)
(on-error error)))))))
;;;; Overlay
(defn show-overlay
+6
View File
@@ -144,3 +144,9 @@
{:events [:reload-status-nav-color]}
[{:keys [db]} view-id]
{:reload-status-nav-color-fx (or view-id (:view-id db))})
(defn open-share
[_ [config]]
{:fx [[:effects.share/open config]]})
(rf/reg-event-fx :open-share open-share)
+7 -1
View File
@@ -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
+15 -1
View File
@@ -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
@@ -233,6 +241,12 @@
:message-pin-enabled message-pin-enabled
:can-delete-message-for-everyone? can-delete-message-for-everyone?})))
(re-frame/reg-sub
:chats/current-chat-one-to-one?
:<- [:chats/current-raw-chat]
(fn [{:keys [chat-type]}]
(= chat-type constants/one-to-one-chat-type)))
(re-frame/reg-sub
:chats/photo-path
:<- [:contacts/contacts]
+7
View File
@@ -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]
+6
View File
@@ -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]
+15
View File
@@ -95,6 +95,21 @@
:<- [:wallet/wallet-send]
:-> :token)
(rf/reg-sub
:wallet/wallet-send-disabled-from-chain-ids
:<- [:wallet/wallet-send]
:-> :disabled-from-chain-ids)
(rf/reg-sub
:wallet/wallet-send-from-values-by-chain
:<- [:wallet/wallet-send]
:-> :from-values-by-chain)
(rf/reg-sub
:wallet/wallet-send-to-values-by-chain
:<- [:wallet/wallet-send]
:-> :to-values-by-chain)
(rf/reg-sub
:wallet/wallet-send-loading-suggested-routes?
:<- [:wallet/wallet-send]
+1 -1
View File
@@ -46,7 +46,7 @@
with the 1st 5 characters of the encoded data followed by an ellipsis
followed by the last 10 characters of the compressed public key"
[universal-profile-url]
(when-let [re-find-result (re-find #"^https://(status.app/u/)(.*)#(.*)$" universal-profile-url)]
(when-let [re-find-result (re-find #"^https://(status.app/u/)(.*)#(.*)$" (str universal-profile-url))]
(let [[_whole-url base-url encoded-data public-key] re-find-result]
(when (> (count public-key) 9)
(let [first-part-of-encoded-data (subs encoded-data 0 5)
+4 -1
View File
@@ -35,4 +35,7 @@
"https://status.app/uwu/abcdefg#zQ3shPrnUhhR42JJn3QdhodGest8w8MjiH8hPaimrdYpzeFUa"))))
(testing "Ensure the function returns nil when given a public key shorter than 10 characters"
(is (nil? (utils.address/get-abbreviated-profile-url "https://status.app/u/abcdefg#012345678")))))
(is (nil? (utils.address/get-abbreviated-profile-url "https://status.app/u/abcdefg#012345678"))))
(testing "Ensure the function returns nil when given nil"
(is (nil? (utils.address/get-abbreviated-profile-url nil)))))
+7
View File
@@ -0,0 +1,7 @@
(ns utils.vector)
(defn insert-element-at
[data element index]
(let [before (take index data)
after (drop index data)]
(vec (concat before [element] after))))
+17
View File
@@ -0,0 +1,17 @@
(ns utils.vector-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[utils.vector :as vector]))
(deftest test-insert-element-at
(testing "Inserting into an empty vector"
(is (= [42] (vector/insert-element-at [] 42 0))))
(testing "Inserting at the beginning of a vector"
(is (= [42 1 2 3] (vector/insert-element-at [1 2 3] 42 0))))
(testing "Inserting in the middle of a vector"
(is (= [1 42 2 3] (vector/insert-element-at [1 2 3] 42 1))))
(testing "Inserting at the end of a vector"
(is (= [1 2 3 42] (vector/insert-element-at [1 2 3] 42 3)))))
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.179.3",
"commit-sha1": "b744ff4386751bd3e7cb8e30164678a8d557fbc2",
"src-sha256": "1yj76khas4g8xbgp2ymr1hwga662hsjmzpgb95x34vl42hdpirns"
"version": "v0.179.4",
"commit-sha1": "d246699c5e1c19b3f905a6c10c3526d2004de273",
"src-sha256": "0gnnx70g8g8yhn29qj58if91fflgl0q2ry0wipzvvpdxd07hpxin"
}
@@ -404,6 +404,8 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(703495)
@marks.xfail(
reason="Chat is not unmuted after expected time: https://github.com/status-im/status-mobile/issues/19627")
def test_group_chat_mute_chat(self):
[self.homes[i].navigate_back_to_home_view() for i in range(3)]
@@ -782,7 +782,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
self.channel_1.just_fyi("Receiver is checking if initial messages were delivered")
for message in message_to_edit, message_to_delete:
if not self.channel_1.chat_element_by_text(message).is_element_displayed(30):
self.channel_1.driver.fail("Message '%s' was not received")
self.channel_1.driver.fail("Message '%s' was not received" % message)
self.channel_2.just_fyi("Turning on airplane mode and editing/deleting messages")
self.channel_2.driver.set_network_connection(ConnectionType.AIRPLANE_MODE)
@@ -52,8 +52,8 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase):
"Status mobile QA community max",
"https://status.app/c/G1AAAGR0G-IRb2YJD4lRXwLusAFnGrDHGNl6Wt55MIARwVYvarnO873011-fdVSz1kHSan-qq0G96vOaMqyTRhJnQV74KCUr#zQ3shb9irJR66rhG1E8sQZX8pDU3dpGm4daYSmPVDd2e73ewE":
"Open community for e2e",
"https://status.app/c/G00AAGS9TbI9mSR-ZNmFrhRjNuEeXAAbcAIUaLLJyjMOG3ACJQ12oIHD78QhzO9s_T5bUeU7rnATWJg3mGgTUemrAg==#zQ3shspPKCZ1VPVQ9dLXGufUGvGphjxVwrcZ6rkZc7S39T4b3":
"closed community"
"https://status.app/c/GzAAAORtwyW4xNWM4td0F7hOnYZ1apSqCCRUUR0qxD19n3Ec97fX_aIVIGFWbdUM#zQ3shk6dgK8dYWWSC4m8Jj5c91zyfhfj1fFkgypS8D9gsXkrK":
"Closed community"
}
for url, text in closed_community_urls.items():
self.channel.just_fyi("Opening community '%s' by the url %s" % (text, url))
@@ -97,8 +97,8 @@ class TestDeepLinksOneDevice(MultipleSharedDeviceTestCase):
"Status mobile QA community max",
"status.app://c/G1AAAGR0G-IRb2YJD4lRXwLusAFnGrDHGNl6Wt55MIARwVYvarnO873011-fdVSz1kHSan-qq0G96vOaMqyTRhJnQV74KCUr#zQ3shb9irJR66rhG1E8sQZX8pDU3dpGm4daYSmPVDd2e73ewE":
"Open community for e2e",
"status.app://c/G00AAGS9TbI9mSR-ZNmFrhRjNuEeXAAbcAIUaLLJyjMOG3ACJQ12oIHD78QhzO9s_T5bUeU7rnATWJg3mGgTUemrAg==#zQ3shspPKCZ1VPVQ9dLXGufUGvGphjxVwrcZ6rkZc7S39T4b3":
"closed community"
"status.app://c/GzAAAORtwyW4xNWM4td0F7hOnYZ1apSqCCRUUR0qxD19n3Ec97fX_aIVIGFWbdUM#zQ3shk6dgK8dYWWSC4m8Jj5c91zyfhfj1fFkgypS8D9gsXkrK":
"Closed community"
}
for link, text in community_links.items():
self.channel.just_fyi("Opening community '%s' by the link %s" % (text, link))
+3 -4
View File
@@ -386,7 +386,6 @@ class CommunityView(HomeView):
self.leave_community_button = Button(self.driver, translation_id="leave-community")
self.edit_community_button = Button(self.driver, translation_id="edit-community")
self.share_community_button = Button(self.driver, accessibility_id="share-community")
self.share_community_link_button = Button(self.driver, accessibility_id="share-community-link")
# Members
self.invite_people_button = Button(self.driver, accessibility_id="community-invite-people")
@@ -500,9 +499,9 @@ 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()
xpath = "//*[@content-desc='user-avatar']/following-sibling::android.widget.TextView[@text='%s']" % user_name
Button(self.driver, xpath=xpath).click()
self.share_invite_button.click()
class PreviewMessage(ChatElementByText):
+13 -2
View File
@@ -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,15 @@
"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",
"dont-auto-recalculate-network": "Don't auto recalculate {{network}}",
"send-from-network": "Send from {{network}}",
"define-amount-sent-from-network": "Define amount sent from {{network}}"
}