Compare commits

...
Author SHA1 Message Date
pavloburykh 6397a7fdd8 testing native token not found error fix 2024-10-11 14:03:38 +03:00
Siddarth Kumar 66f177370a nix: fix sha256 of nixpkgs release 24-05 (#21416) 2024-10-11 15:16:25 +05:30
Volodymyr Kozieiev ad7d58788f Missed key added to token returned from assets list (#21402) 2024-10-10 11:55:03 +01:00
Alexander ad4ea0783f A black square background at the end of Swap confirmation screen (#21372) 2024-10-09 15:37:38 -03:00
Brian Sztamfater 5c44cb6399 fix(swap): display very small max values, fix scientific notation, handle decimal mismatch when changing tokens, display long numbers (#21388)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-10-09 13:49:45 -03:00
Ulises Manuel a9e0b3dd6b fix(wallet): Exception when navigating back from route generation (#21381)
- Slightly improved code style
2024-10-08 19:05:29 -06:00
Ulises Manuel 131da5f444 fix(community): Last message overlapped with "join community" button (#21370) 2024-10-08 18:50:29 -06:00
Brian Sztamfater 874ececc47 fix(swap): update max value when the account is switched (#21395)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-10-08 18:16:38 -03:00
Yevheniia Berdnyk 5f1d74a77a e2e: added tests for fallback flow 2024-10-08 17:49:27 +03:00
Brian Sztamfater 00b0d75fc4 fix(wallet): mercuryo on-ramp (#20962)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-10-08 09:43:56 -03:00
Siddarth Kumar 4b93c8ff43 nix: use multiple nixpkgs sources (#21374) 2024-10-08 17:51:30 +05:30
flexsurfer 80ad2b8fca [#21318] Keycard - Allow user to initiate Profile key pair migration on an empty Keycard (#21359) 2024-10-07 20:15:19 +02:00
Alexander a8bc93eb17 Account selection drawer with balance tag (#21356) 2024-10-07 14:57:23 -03:00
Parvesh Monu 9d3fc4286e fix syncing screen only shows paired label after the restart (#21315)
https://github.com/status-im/status-go/compare/effde33d...12c4e869
2024-10-07 22:35:46 +08:00
Siddarth Kumar 16fba373c9 nix: add preBuild phase for generating files (#21390)
https://github.com/status-im/status-go/compare/effde33d...ba37c32c

https://github.com/status-im/status-go/pull/5920

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2024-10-07 17:19:37 +05:30
Yevheniia Berdnyk 9c3ff8c88c e2e: fixes for edit/delete message in tests 2024-10-04 17:04:18 +03:00
flexsurfer 2525620730 Update import-assets.md typo 2024-10-04 14:32:46 +02:00
Ulises Manuel 6dde1d7b74 fix(onboarding): Increase hit area for all selectors (#21355)
* Increase hit-slop for all selectors

* Remove unnecessary `:f>` wrapper
2024-10-03 13:35:47 -06:00
Sean Hagstrom 1691e2fe34 chore: remove run-tests from wallet-send event tests (#21373) 2024-10-03 12:56:45 +01:00
Parvesh Monu b536d20713 Add syncing notification to System Tab (#21314) 2024-10-03 07:11:10 +05:30
Ulises Manuel ca3720be0a fix(communities): token-gated community info text (#21338) 2024-10-02 14:57:45 -06:00
Ulises Manuel 63d0aaca2a feat(chats): Implement a simpler chat screen (#21313)
* Change `defonce ^:const` -> `def ^:const` in `status-im.contexts.chat.messenger.messages.list.view` to be more REPL friendly

* Remove animations from `status-im.contexts.chat.messenger.messages.pin.banner.style`

* Simplify placeholder for messages

* Replace shared-value with a ratom usage since no animations were performed.

* Fix the style for the new designs and remove now unnecessary animations

* Improve chat component implementation

    The previous implementation has two main problems:
        1. It is creating atoms and ratoms in non-form-2-components.
        2. Conditionally uses a hook: `rn/use-mount` in `lazy-chat-screen`.

    The new implementation fixes the previous problems, improves the readability and passes only the props needed to its children.

* Remove action buttons from the chat screen

* Remove now unused code

* Replace reanimated/view -> rn/view where no animations are made
2024-10-02 14:35:23 -06:00
93 changed files with 1669 additions and 1111 deletions
+1 -1
View File
@@ -19,5 +19,5 @@
Make sure to compress images before using into project.
```
make shell
./scrips/compress_image.sh image_path
./scripts/compress_image.sh image_path
```
-1
View File
@@ -51,7 +51,6 @@ in {
};
# Package version adjustments
gradle = super.gradle_8.override { java = super.openjdk17_headless; };
nodejs = super.nodejs_20;
ruby = super.ruby_3_1;
yarn = super.yarn.override { nodejs = super.nodejs_20; };
+18 -2
View File
@@ -6,8 +6,16 @@ let
# For testing local version of nixpkgs
#nixpkgsSrc = (import <nixpkgs> { }).lib.cleanSource "/home/jakubgs/work/nixpkgs";
# We use a commit from the unstable channel of nixpkgs for gradle 8.8
# We follow release 24-05 of nixpkgs
# https://github.com/NixOS/nixpkgs/releases/tag/24.05
nixpkgsSrc = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/df27247e6f3e636c119e2610bf12d38b5e98cc79.tar.gz";
sha256 = "sha256:0bbvimk7xb7akrx106mmsiwf9nzxnssisqmqffla03zz51d0kz2n";
};
# FIXME: remove this additional source when nixpkgs includes gradle 8.8 in stable channel
# We use a commit from the unstable channel of nixpkgs for gradle 8.8
gradleNixpkgsSrc = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/48d567fc7b299de90f70a48e4263e31f690ba03e.tar.gz";
sha256 = "sha256:0zynbk52khdfhg4qfv26h3r5156xff5p0cga2cin7b07i7lqminh";
};
@@ -21,6 +29,14 @@ let
# Override some packages and utilities
pkgsOverlay = import ./overlay.nix;
# FIXME: remove this additional source when nixpkgs includes gradle 8.8 in stable channel
gradleOverlay = final: prev: {
gradle = (import gradleNixpkgsSrc {
inherit (prev) system;
config = defaultConfig // config;
}).gradle_8.override { java = prev.openjdk17_headless; };
};
# Fix for lack of Android SDK for M1 Macs.
systemOverride = let
inherit (builtins) currentSystem getEnv;
@@ -35,5 +51,5 @@ in
(import nixpkgsSrc) {
config = defaultConfig // config;
system = systemOverride;
overlays = [ pkgsOverlay ];
overlays = [ pkgsOverlay gradleOverlay ];
}
+7 -1
View File
@@ -1,4 +1,5 @@
{ lib, stdenv, meta, source, buildGoPackage }:
{ lib, stdenv, meta, source, buildGoPackage,
go-bindata, mockgen, protoc-gen-go, protobuf3_20 }:
buildGoPackage {
pname = source.repo;
@@ -7,6 +8,10 @@ buildGoPackage {
inherit meta;
inherit (source) src goPackagePath;
nativeBuildInputs = [
go-bindata mockgen protoc-gen-go protobuf3_20
];
phases = ["unpackPhase" "configurePhase" "buildPhase"];
# https://pkg.go.dev/net#hdr-Name_Resolution
@@ -22,6 +27,7 @@ buildGoPackage {
preBuild = ''
pushd go/src/$goPackagePath
go run cmd/library/*.go > $NIX_BUILD_TOP/main.go
make generate SHELL=$SHELL GO111MODULE=on GO_GENERATE_CMD='go generate'
popd
'';
+12 -3
View File
@@ -1,5 +1,6 @@
{ callPackage, lib, buildGoPackage
, androidPkgs, openjdk, gomobile, xcodeWrapper, removeReferencesTo
, go-bindata, mockgen, protobuf3_20, protoc-gen-go
, meta
, source
, platform ? "android"
@@ -15,7 +16,7 @@ let
isAndroid = platform == "android";
enforceXCodeAvailable = callPackage ./enforceXCodeAvailable.nix { };
in buildGoPackage {
in buildGoPackage rec {
pname = source.repo;
version = "${source.cleanVersion}-${source.shortRev}-${platform}";
@@ -27,8 +28,9 @@ in buildGoPackage {
__noChroot = isIOS;
extraSrcPaths = [ gomobile ];
nativeBuildInputs = [ gomobile removeReferencesTo ]
++ optional isAndroid openjdk
nativeBuildInputs = [
gomobile removeReferencesTo go-bindata mockgen protoc-gen-go protobuf3_20
] ++ optional isAndroid openjdk
++ optional isIOS xcodeWrapper;
ldflags = goBuildLdFlags;
@@ -44,6 +46,13 @@ in buildGoPackage {
# TODO: try removing when go is upgraded to 1.22
GODEBUG = "netdns=cgo+2";
preBuild = ''
echo 'Generate static files'
pushd go/src/$goPackagePath
make generate SHELL=$SHELL GO111MODULE=on GO_GENERATE_CMD='go generate'
popd
'';
buildPhase = ''
runHook preBuild
echo -e "\nBuilding $pname for: ${concatStringsSep "," targets}"
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@@ -1,47 +0,0 @@
import { useDerivedValue, withTiming, interpolate } from 'react-native-reanimated';
export function navigationHeaderOpacity(distanceFromListTop, isAllLoaded, isCalculationsComplete, startPosition) {
return useDerivedValue(function () {
'worklet';
const isCalculationsCompleteValue = isCalculationsComplete.value;
if (distanceFromListTop.value < startPosition && isAllLoaded.value) {
return isCalculationsCompleteValue ? withTiming(0) : 0;
} else {
return isCalculationsCompleteValue ? withTiming(1) : 1;
}
});
}
export function navigationHeaderPosition(distanceFromListTop, isAllLoaded, topBarHeight, startPosition) {
return useDerivedValue(function () {
'worklet';
return distanceFromListTop.value < startPosition && isAllLoaded.value ? withTiming(topBarHeight) : withTiming(0);
});
}
export function navigationButtonsCompleteOpacity(isCalculationComplete) {
return useDerivedValue(function () {
'worklet';
return isCalculationComplete.value ? withTiming(1) : 0;
});
}
export function interpolateNavigationViewOpacity(props) {
return useDerivedValue(function () {
'worklet';
const {
'all-loaded?': isAllLoaded,
'end-position': endPosition,
'start-position': startPosition,
'distance-from-list-top': distanceFromListTop,
} = props;
if (isAllLoaded.value) {
return interpolate(distanceFromListTop.value, [startPosition, endPosition], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
} else {
return 1;
}
});
}
@@ -1,15 +0,0 @@
import { useDerivedValue } from 'react-native-reanimated';
export function placeholderOpacity(isCalculationsComplete) {
return useDerivedValue(function () {
'worklet';
return isCalculationsComplete.value ? 0 : 1;
});
}
export function placeholderZIndex(isCalculationsComplete) {
return useDerivedValue(function () {
'worklet';
return isCalculationsComplete.value ? 0 : 2;
});
}
+4 -3
View File
@@ -256,9 +256,10 @@
{:events [:pairing/pair-and-sync]}
[cofx installation-id]
{:fx [[:json-rpc/call
[{:method "wakuext_enableInstallationAndSync"
:params [{:installationId installation-id}]
:on-success #(log/debug "successfully synced devices")}]]]})
[{:method "wakuext_enableInstallationAndSync"
:params [{:installationId installation-id}]
:js-response true
:on-success [:sanitize-messages-and-process-response]}]]]})
(rf/defn enable-installation-success
{:events [:pairing.callback/enable-installation-success]}
@@ -65,7 +65,7 @@
:type type
:include-button? include-button?})
style)}
[icons/icon icon
[icons/icon (or icon :i/info)
{:color (style/get-color-by-type theme type :icon)
:no-color no-icon-color?
:size (or icon-size 16)
@@ -1,6 +1,7 @@
(ns quo.components.list-items.network-list.view
(:require
[clojure.string :as string]
[quo.components.icon :as icon]
[quo.components.list-items.network-list.style :as style]
[quo.components.markdown.text :as text]
[quo.theme :as quo.theme]
@@ -42,8 +43,8 @@
[:map {:closed true}
[:network-image :int]
[:label :string]
[:fiat-value :string]
[:token-value :string]
[:fiat-value {:optional true} [:maybe :string]]
[:token-value {:optional true} [:maybe :string]]
[:container-style {:optional true} [:maybe :map]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:state {:optional true} [:enum :pressed :active :disabled :default]]
@@ -51,7 +52,7 @@
:any])
(defn- view-internal
[{:keys [on-press container-style state customization-color]
[{:keys [fiat-value token-value on-press container-style state customization-color]
:as props
:or {customization-color :blue}}]
(let [theme (quo.theme/use-theme)
@@ -67,6 +68,8 @@
:on-press (when-not (= state :disabled) on-press)
:accessibility-label :network-list}
[info props]
[values props]]))
(if (or token-value fiat-value)
[values props]
[icon/icon :i/chevron-right])]))
(def view (schema/instrument #'view-internal ?schema))
+1 -3
View File
@@ -4,12 +4,10 @@
[container-style]
(merge container-style
{:flex-direction :row
:flex 1
:align-items :flex-start}))
(def index
{:margin-left 5})
(def text-container
{:margin-left 8
:flex 1})
{:margin-left 8})
@@ -32,7 +32,8 @@
[rn/pressable
(when-not disabled?
{:on-press on-press
:allow-multiple-presses? true})
:allow-multiple-presses? true
:hit-slop 12})
[rn/view
{:style outer-styles
:needs-offscreen-alpha-compositing true
@@ -75,3 +75,16 @@
(def fiat-amount
{:color colors/neutral-50})
(def gradient-common
{:width 64
:position :absolute
:top 0
:bottom 0
:z-index 1})
(def gradient-start
(assoc gradient-common :left 0))
(def gradient-end
(assoc gradient-common :right 0))
+91 -32
View File
@@ -11,6 +11,7 @@
[quo.foundations.colors :as colors]
quo.theme
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[schema.core :as schema]))
(def ?schema
@@ -18,6 +19,7 @@
[:catn
[:props
[:map {:closed true}
[:get-ref {:optional true} [:maybe fn?]]
[:type {:optional true} [:maybe [:enum :pay :receive]]]
[:status {:optional true} [:maybe [:enum :default :typing :disabled :loading]]]
[:token {:optional true} [:maybe :string]]
@@ -42,26 +44,65 @@
[:container-style {:optional true} [:maybe :map]]]]]
:any])
(def icon-size 32)
(def container-padding (* 2 (:padding (style/row-1 false))))
(def max-cursor-position 5)
(defn view-internal
[{:keys [type status token value fiat-value show-approval-label? error? network-tag-props
approval-label-props default-value auto-focus? input-disabled? enable-swap?
currency-symbol on-change-text show-keyboard?
currency-symbol on-change-text show-keyboard? get-ref
container-style on-swap-press on-token-press on-max-press on-input-focus]}]
(let [theme (quo.theme/use-theme)
pay? (= type :pay)
disabled? (= status :disabled)
loading? (= status :loading)
typing? (= status :typing)
controlled-input? (some? value)
input-ref (rn/use-ref-atom nil)
set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)) [])
focus-input (rn/use-callback (fn []
(some-> @input-ref
(oops/ocall "focus")))
[input-ref])]
(let [theme (quo.theme/use-theme)
pay? (= type :pay)
disabled? (= status :disabled)
loading? (= status :loading)
typing? (= status :typing)
controlled-input? (some? value)
[container-width
set-container-width] (rn/use-state)
[overflow?
set-overflow?] (rn/use-state false)
[cursor-close-to-start?
set-cursor-close-to-start?] (rn/use-state false)
[label-width
set-label-width] (rn/use-state 0)
input-ref (rn/use-ref-atom nil)
set-input-ref (rn/use-callback (fn [ref]
(reset! input-ref ref)
(when get-ref (get-ref ref)))
[])
focus-input (rn/use-callback (fn []
(some-> @input-ref
(oops/ocall "focus")))
[input-ref])
on-layout-container (rn/use-callback
(fn [e]
(let [width (oops/oget e "nativeEvent.layout.width")]
(set-container-width width))))
on-layout-text-input (rn/use-callback
(fn [e]
(let [width (oops/oget e "nativeEvent.layout.width")
max-width (- container-width
icon-size
container-padding
(* label-width 2))]
(set-overflow? (> width max-width))))
[container-width label-width])
on-layout-label (rn/use-callback
(fn [e]
(let [width (oops/oget e "nativeEvent.layout.width")]
(set-label-width width))))
on-selection-change (rn/use-callback
(fn [e]
(let [selection-start (oops/oget e
"nativeEvent.selection.start")]
(set-cursor-close-to-start?
(< selection-start max-cursor-position)))))]
[rn/view
{:style container-style
:accessibility-label :swap-input}
:accessibility-label :swap-input
:on-layout on-layout-container}
[rn/view {:style (style/content typing? theme)}
[rn/view
{:style (style/row-1 loading?)}
@@ -75,25 +116,43 @@
[rn/pressable
{:style style/input-container
:on-press focus-input}
[rn/text-input
(cond-> {:ref set-input-ref
:style (style/input disabled? error? theme)
:placeholder-text-color (colors/theme-colors colors/neutral-40
colors/neutral-50
theme)
:keyboard-type :numeric
:editable (not input-disabled?)
:auto-focus auto-focus?
:on-focus on-input-focus
:on-change-text on-change-text
:show-soft-input-on-focus show-keyboard?
:default-value default-value
:placeholder "0"}
controlled-input? (assoc :value value))]
[rn/view {:style {:flex-shrink 1}}
(when (and overflow? typing? (not cursor-close-to-start?))
[linear-gradient/linear-gradient
{:start {:x 0 :y 0}
:end {:x 1 :y 0}
:colors [(colors/theme-colors colors/white colors/neutral-100 theme)
(colors/theme-colors colors/white-opa-10 colors/neutral-100-opa-10 theme)]
:style style/gradient-start}])
(when (and overflow? disabled?)
[linear-gradient/linear-gradient
{:start {:x 0 :y 0}
:end {:x 1 :y 0}
:colors [(colors/theme-colors colors/white-opa-10 colors/neutral-100-opa-10 theme)
(colors/theme-colors colors/white colors/neutral-100 theme)]
:style style/gradient-end}])
[rn/text-input
(cond-> {:ref set-input-ref
:style (style/input disabled? error? theme)
:placeholder-text-color (colors/theme-colors colors/neutral-40
colors/neutral-50
theme)
:keyboard-type :numeric
:editable (not input-disabled?)
:auto-focus auto-focus?
:on-focus on-input-focus
:on-change-text on-change-text
:on-layout on-layout-text-input
:on-selection-change on-selection-change
:show-soft-input-on-focus show-keyboard?
:default-value default-value
:placeholder "0"}
controlled-input? (assoc :value value))]]
[text/text
{:size :paragraph-2
:weight :semi-bold
:style (style/token-symbol theme)}
{:size :paragraph-2
:weight :semi-bold
:style (style/token-symbol theme)
:on-layout on-layout-label}
token]]
(when (and pay? enable-swap?)
[buttons/button
+1
View File
@@ -17,6 +17,7 @@
:generate-keys1 (js/require "../resources/images/ui2/generating-keys-1.png")
:ethereum-address (js/require "../resources/images/ui2/ethereum-address.png")
:use-keycard (js/require "../resources/images/ui2/keycard.png")
:check-your-keycard (js/require "../resources/images/ui2/check-your-keycard.png")
:onboarding-illustration (js/require "../resources/images/ui2/onboarding_illustration.png")
:qr-code (js/require "../resources/images/ui2/qr-code.png")
:keycard-logo (js/require "../resources/images/ui2/keycard-logo.png")
@@ -17,7 +17,7 @@
user])
(defn view
[{:keys [layout-height]}]
[layout-height]
(let [suggestions (rf/sub [:chat/mention-suggestions])
suggestions? (seq suggestions)
theme (quo.theme/use-theme)
@@ -35,7 +35,7 @@
:default-value default-value}])))
(defn view
[props]
[layout-height]
(let [theme (quo.theme/use-theme)
bottom (safe-area/get-bottom)
input-ref (rn/use-ref-atom nil)
@@ -43,7 +43,7 @@
(rf/dispatch [:chat/set-input-ref value])
(reset! input-ref value)))]
[rn/view {:style {:margin-bottom bottom}}
[mentions/view props]
[mentions/view layout-height]
[quo/separator]
[rn/view {:style {:padding-horizontal 20 :padding-top 20}}
[:<>
@@ -3,15 +3,4 @@
;;;; Navigation
(def ^:const top-bar-height 56)
(def ^:const pinned-banner-height 40)
(def ^:const header-container-top-margin 48)
(def ^:const header-container-radius 20)
(def ^:const header-animation-distance 20)
(def ^:const content-animation-start-position-android 130)
(def ^:const content-animation-start-position-ios 124)
;; Note - We should also consider height of bio for banner animation starting position
;; Todo - Should be updated once New-profile implemation is complete
(def ^:const pinned-banner-animation-start-position 148)
(def ^:const default-extrapolation-option
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})
@@ -11,25 +11,23 @@
(defn view
[{:keys [contact-id]}]
(let [customization-color (rf/sub [:profile/customization-color])
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity
contact-id])
{:keys [contact-request-state community-id]} (rf/sub [:chats/current-chat-chat-view])
chat-type (rf/sub [:chats/chat-type])
community-chat? (= chat-type :community-chat)
joined (when community-chat?
(rf/sub [:communities/community-joined
community-id]))
pending? (when community-chat?
(rf/sub [:communities/my-pending-request-to-join
community-id]))
contact-request-send? (or (not contact-request-state)
(= contact-request-state
constants/contact-request-state-none))
contact-request-received? (= contact-request-state
constants/contact-request-state-received)
contact-request-pending? (= contact-request-state
constants/contact-request-state-sent)]
(let [customization-color (rf/sub [:profile/customization-color])
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity contact-id])
{:keys [contact-request-state
community-id]} (rf/sub [:chats/current-chat-chat-view])
chat-type (rf/sub [:chats/chat-type])
community-chat? (= chat-type :community-chat)
joined (when community-chat?
(rf/sub [:communities/community-joined community-id]))
pending? (when community-chat?
(rf/sub [:communities/my-pending-request-to-join community-id]))
contact-request-send? (or (not contact-request-state)
(= contact-request-state
constants/contact-request-state-none))
contact-request-received? (= contact-request-state
constants/contact-request-state-received)
contact-request-pending? (= contact-request-state
constants/contact-request-state-sent)]
[rn/view {:style style/container}
[quo/permission-context
{:blur? true
@@ -1,11 +1,11 @@
(ns status-im.contexts.chat.messenger.messages.list.style
(:require
[quo.foundations.colors :as colors]
[quo.foundations.shadows :as shadows]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
(def permission-context-height 46)
(def distance-from-last-message 4)
(defn keyboard-avoiding-container
[theme]
@@ -13,70 +13,13 @@
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:z-index 2})
(defn background-container
[background-color background-opacity top-margin]
(reanimated/apply-animations-to-style
{:opacity background-opacity}
{:background-color background-color
:position :absolute
:top 0
:left 0
:right 0
:height (+ top-margin messages.constants/header-container-radius)}))
(def permission-context-sheet {:flex 3}) ; Pushes composer to bottom
(defn header-bottom-container
[bottom top-margin]
(reanimated/apply-animations-to-style
{:bottom bottom}
{:margin-top top-margin}))
(defn header-bottom-part
[theme]
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:padding-horizontal 20
:border-radius 20})
(defn header-bottom-shadow
[theme]
(assoc
(shadows/get 2 theme :inverted)
:left 0
:right 0
:height 40
:position :absolute
:border-radius 20))
(defn header-image
[scale top left theme]
(reanimated/apply-animations-to-style
{:transform [{:scale scale}]
:top top
:left left}
{:position :absolute
:border-width 4
:border-radius 50
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:border-color (colors/theme-colors colors/white colors/neutral-95 theme)}))
(defn user-name-container
[top left]
(reanimated/apply-animations-to-style
{:top top
:left left}
{:z-index -1}))
(def user-name
{:align-items :center
:flex-direction :row
:margin-top 52})
(defn bio-and-actions
[top]
(reanimated/apply-animations-to-style
{:top top}
{:row-gap 16}))
(defn permission-context-sheet
[margin-bottom?]
{:flex 3 ;; Pushes composer to bottom
:margin-bottom (when margin-bottom? permission-context-height)})
(defn list-paddings
[add-padding-bottom?]
(let [{:keys [top bottom]} (safe-area/get-insets)]
;; WARNING: the flat-list is reversed, so the paddings are applied inverted
{:padding-top (if add-padding-bottom?
(+ distance-from-last-message permission-context-height bottom)
distance-from-last-message)
:padding-bottom (+ top messages.constants/top-bar-height)}))
@@ -1,6 +1,5 @@
(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]
@@ -11,11 +10,9 @@
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.common.home.actions.view :as home.actions]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.composer.constants :as composer.constants]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]
[status-im.contexts.chat.messenger.messages.content.view :as message]
[status-im.contexts.chat.messenger.messages.list.state :as state]
[status-im.contexts.chat.messenger.messages.list.style :as style]
@@ -25,8 +22,7 @@
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.messages :as worklets]))
(defonce ^:const distance-from-last-message 4)
(defonce ^:const loading-indicator-page-loading-height 100)
(def ^:const loading-indicator-page-loading-height 100)
(defn list-key-fn [{:keys [message-id value]}] (or message-id value))
(defn list-ref [ref] (reset! state/messages-list-ref ref))
@@ -60,44 +56,17 @@
on-loaded])
(if platform/low-device? 700 100)))))
(defn- contact-icon
[{:keys [ens-verified added?]} theme]
(when (or ens-verified added?)
[rn/view
{:style {:margin-left 4}}
(if ens-verified
[quo/icon :i/verified
{:no-color true
:size 20
:color (colors/theme-colors
(colors/custom-color :success 50)
(colors/custom-color :success 60)
theme)}]
(when added?
[quo/icon :i/contact
{:no-color true
:size 20
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)}]))]))
(defn- skeleton-list-props
[content parent-height animated?]
{:content content
:parent-height parent-height
:animated? animated?})
(defn loading-view
[chat-id {:keys [window-height]}]
[{:keys [chat-id window-height]}]
(let [messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
loading-first-page? (= (count messages) 0)
top-spacing (if loading-first-page?
0
(+ messages.constants/top-bar-height (safe-area/get-top)))
parent-height (if loading-first-page?
window-height
loading-indicator-page-loading-height)]
[rn/view {:padding-top top-spacing}
;; Don't use animated loading skeleton https://github.com/status-im/status-mobile/issues/17426
[quo/skeleton-list (skeleton-list-props :messages parent-height false)]]))
[quo/skeleton-list
{:content :messages
:parent-height parent-height
:animated? false}]))
(defn header-height
[{:keys [insets able-to-send-message? images reply edit link-previews? input-content-height]}]
@@ -125,84 +94,6 @@
(+ (:bottom insets)))
(- 70 (:bottom insets))))
(defn list-header
[insets able-to-send-message?]
(let [header-data {:insets insets
:able-to-send-message? able-to-send-message?
:input-content-height (:input-content-height (rf/sub [:chats/current-chat-input]))
:images (rf/sub [:chats/sending-image])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])
:link-previews? (or (rf/sub [:chats/link-previews?])
(rf/sub [:chats/status-link-previews?]))}]
[rn/view {:style {:height (header-height header-data)}}]))
(defn list-footer-avatar
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color
emoji chat-type chat-name empty-chat?]}]
(let [scale (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[1 0.4]
messages.constants/default-extrapolation-option)
top (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[-44 -12]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[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)}
(cond
community-channel?
[quo/channel-avatar
{:size :size-80
:full-name chat-name
:customization-color color
:emoji (when-not (string/blank? emoji)
(string/trim emoji))}]
group-chat
[quo/group-avatar
{:customization-color color
:size :size-80
:picture profile-picture
:chat-name chat-name}]
:else
[quo/user-avatar
{:full-name display-name
:online? online?
:profile-picture profile-picture
:size :big}])]))
(defn chat-display-name
[{:keys [distance-from-list-top display-name contact theme empty-chat?]}]
(let [top (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[0 -35]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[0 40]
messages.constants/default-extrapolation-option)]
[reanimated/view
{:style (style/user-name-container top left)}
[rn/view
{:style style/user-name}
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:flex-shrink 1}
:number-of-lines 1}
display-name]
[contact-icon contact theme]]]))
(defn actions
[chat-id cover-bg-color]
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
@@ -236,101 +127,12 @@
chat-type
muted?)))}]}]))
(defn bio-and-actions
[{:keys [distance-from-list-top bio chat-id customization-color empty-chat? description]}]
(let [has-bio (seq (or bio description))
top (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[(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 (or bio description)])
[actions chat-id customization-color]]))
(defn footer-component
[{:keys [chat distance-from-list-top customization-color]}]
(let [theme (quo.theme/use-theme)
{:keys [chat-id chat-name emoji chat-type
group-chat color description
empty-chat?]} 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 (if group-chat
(rf/sub [:chats/group-chat-image chat-id])
(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)}]
[reanimated/view {:style (style/header-bottom-container bottom top-margin)}
[rn/view {:style (style/header-bottom-shadow theme)}]
[rn/view {:style (style/header-bottom-part theme)}
[list-footer-avatar
{:distance-from-list-top distance-from-list-top
:display-name display-name
:online? online?
:theme theme
:profile-picture photo-path
:group-chat group-chat
:color color
:emoji emoji
:chat-type chat-type
:chat-name chat-name
:empty-chat? empty-chat?}]
[chat-display-name
{:distance-from-list-top distance-from-list-top
:display-name display-name
:theme theme
:contact contact
:group-chat group-chat
:empty-chat? empty-chat?}]
[bio-and-actions
{:distance-from-list-top distance-from-list-top
:bio bio
:chat-id chat-id
:customization-color customization-color
:description description
:empty-chat? empty-chat?}]]]]))
(defn list-footer
[props]
(let [chat-id (get-in props [:chat :chat-id])
loading-messages? (rf/sub [:chats/loading-messages? chat-id])
(defn more-messages-loader
[{:keys [chat-id] :as props}]
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
all-loaded? (rf/sub [:chats/all-loaded? chat-id])]
[:<>
(if (or loading-messages? (not all-loaded?))
[loading-view chat-id props]
[footer-component props])]))
(when (or loading-messages? (not all-loaded?))
[loading-view props])))
(defn list-group-chat-header
[{:keys [chat-id invitation-admin]}]
@@ -359,8 +161,7 @@
(reset! content-height content-height-new)))))
(defn on-layout
[{:keys [event layout-height distance-atom distance-from-list-top
chat-screen-layout-calculations-complete?]}]
[{:keys [event layout-height distance-atom distance-from-list-top on-layout-done?]}]
(let [layout-height-new (oops/oget event "nativeEvent.layout.height")
change (- layout-height-new @layout-height)
new-distance (- @distance-atom change)]
@@ -368,8 +169,7 @@
(reanimated/set-shared-value distance-from-list-top new-distance)
(reset! distance-atom new-distance)
(reset! layout-height layout-height-new))
(when-not (reanimated/get-shared-value chat-screen-layout-calculations-complete?)
(reanimated/set-shared-value chat-screen-layout-calculations-complete? true))))
(reset! on-layout-done? true)))
(defn on-scroll-fn
[distance-atom layout-height-atom]
@@ -379,29 +179,21 @@
(reset! distance-atom new-distance)))
(defn messages-list-content
[{:keys [insets distance-from-list-top content-height layout-height distance-atom
chat-screen-layout-calculations-complete? chat-list-scroll-y]}]
(let [theme (quo.theme/use-theme)
{:keys [chat-type chat-id] :as chat} (rf/sub [:chats/current-chat-chat-view])
one-to-one-chat? (= chat-type constants/one-to-one-chat-type)
community-channel? (= constants/community-chat-type chat-type)
{contact-customization-color
:customization-color} (when one-to-one-chat?
(rf/sub [:contacts/contact-by-identity chat-id]))
customization-color (cond community-channel?
(or (:color chat)
(rf/sub [:communities/community-color
(:community-id chat)]))
one-to-one-chat? contact-customization-color
:else (or (:color chat) :turquoise))
{:keys [keyboard-shown]} (hooks/use-keyboard)
{window-height :height} (rn/get-window)
context (rf/sub [:chats/current-chat-message-list-view-context])
able-to-send-message? (:able-to-send-message? context)
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
margin-bottom? (and community-channel? (not able-to-send-message?))
recording? (rf/sub [:chats/recording?])]
[rn/view {:style (style/permission-context-sheet margin-bottom?)}
[{:keys [insets distance-from-list-top layout-height chat-list-scroll-y on-layout-done?]}]
(let [content-height (rn/use-ref-atom 0)
distance-atom (rn/use-ref-atom 0)
theme (quo.theme/use-theme)
{:keys [keyboard-shown]} (hooks/use-keyboard)
{:keys [chat-type chat-id]
:as chat} (rf/sub [:chats/current-chat-chat-view])
community-channel? (= constants/community-chat-type chat-type)
{window-height :height} (rn/get-window)
context (rf/sub [:chats/current-chat-message-list-view-context])
able-to-send-message? (:able-to-send-message? context)
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
add-padding-bottom? (and community-channel? (not able-to-send-message?))
recording? (rf/sub [:chats/recording?])]
[rn/view {:style style/permission-context-sheet}
[rn/view {:style {:flex-shrink 1}} ;; Keeps flat list on top
[reanimated/flat-list
{:key-fn list-key-fn
@@ -409,12 +201,9 @@
:bounces false
:header (when (= (:chat-type chat) constants/private-group-chat-type)
[list-group-chat-header chat])
:footer [list-footer
{:theme theme
:chat chat
:window-height window-height
:distance-from-list-top distance-from-list-top
:customization-color customization-color}]
:footer [more-messages-loader
{:chat-id chat-id
:window-height window-height}]
:data messages
:render-data {:theme theme
:context context
@@ -444,15 +233,14 @@
:style {:background-color (colors/theme-colors colors/white
colors/neutral-95
theme)}
:content-container-style {:padding-top distance-from-last-message}
:content-container-style (style/list-paddings add-padding-bottom?)
:inverted true
:on-layout #(on-layout
{:event %
:layout-height layout-height
:distance-atom distance-atom
{:event %
:layout-height layout-height
:distance-atom distance-atom
:distance-from-list-top distance-from-list-top
:chat-screen-layout-calculations-complete?
chat-screen-layout-calculations-complete?})
:on-layout-done? on-layout-done?})
:scroll-enabled (not recording?)
:content-inset-adjustment-behavior :never
:scroll-indicator-insets {:right 1}}]]]))
@@ -1,56 +1,43 @@
(ns status-im.contexts.chat.messenger.messages.navigation.style
(:require
[quo.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(:require [quo.foundations.colors :as colors]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
(defn navigation-view
[navigation-view-height pinned-banner-height]
[navigation-view-height]
{:top 0
:left 0
:right 0
:position :absolute
:pointer-events :box-none
:height (+ navigation-view-height pinned-banner-height)
:height (+ navigation-view-height messages.constants/pinned-banner-height)
:z-index 1})
(defn animated-background-view
[background-opacity navigation-view-height]
(reanimated/apply-animations-to-style
{:opacity background-opacity}
{:height navigation-view-height
:top 0
:left 0
:right 0
:overflow :hidden
:position :absolute}))
(defn background
[navigation-view-height]
{:height navigation-view-height
:top 0
:left 0
:right 0
:overflow :hidden
:position :absolute})
(defn header-container
[top-insets top-bar-height]
[top-insets]
{:margin-top top-insets
:flex-direction :row
:padding-horizontal 20
:overflow :hidden
:height top-bar-height
:height messages.constants/top-bar-height
:align-items :center})
(defn button-animation-container
[opacity-value]
(reanimated/apply-animations-to-style
{:opacity opacity-value}
{}))
;;;; Content
(defn header-content-container
[header-opacity header-position]
(reanimated/apply-animations-to-style
{:transform [{:translate-y header-position}]
:opacity header-opacity}
{:flex-direction :row
:align-items :center
:flex 1
:margin-horizontal 12
:height 40}))
(def header-content-container
{:flex-direction :row
:align-items :center
:flex 1
:margin-horizontal 12
:height 40})
(def header-text-container
{:margin-left 8})
@@ -7,7 +7,6 @@
[re-frame.db]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.common.home.actions.view :as actions]
[status-im.constants :as constants]
@@ -15,44 +14,26 @@
[status-im.contexts.chat.messenger.messages.navigation.style :as style]
[status-im.contexts.chat.messenger.messages.pin.banner.view :as pin.banner]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.messages :as messages.worklets]
[utils.worklets.chat.messenger.navigation :as worklets]))
[utils.re-frame :as rf]))
(defn header-content-container
[{:keys [chat distance-from-list-top all-loaded? chat-screen-layout-calculations-complete?]}]
(let [theme (quo.theme/use-theme)
{:keys [chat-id group-chat chat-type chat-name
emoji 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 "# " chat-name)
:else (str emoji chat-name))
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
photo-path (if group-chat
(rf/sub [:chats/group-chat-image chat-id])
(rf/sub [:chats/photo-path chat-id]))
header-opacity (worklets/navigation-header-opacity
distance-from-list-top
all-loaded?
chat-screen-layout-calculations-complete?
(if platform/ios?
messages.constants/content-animation-start-position-ios
messages.constants/content-animation-start-position-android))
header-position (worklets/navigation-header-position
distance-from-list-top
all-loaded?
messages.constants/top-bar-height
(if platform/ios?
messages.constants/content-animation-start-position-ios
messages.constants/content-animation-start-position-android))
community-channel? (= chat-type constants/community-chat-type)]
[reanimated/view
{:style (style/header-content-container header-opacity header-position)}
[{:keys [chat-id group-chat chat-type chat-name emoji color] :as _chat}]
(let [theme (quo.theme/use-theme)
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))
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
photo-path (if group-chat
(rf/sub [:chats/group-chat-image chat-id])
(rf/sub [:chats/photo-path chat-id]))
community-channel? (= chat-type constants/community-chat-type)]
[rn/view {:style style/header-content-container}
(cond
community-channel?
[quo/channel-avatar
@@ -90,30 +71,11 @@
(i18n/label
(if online? :t/online :t/offline))])]]))
(defn animated-background-and-pinned-banner
[{:keys [chat-id navigation-view-height distance-from-list-top all-loaded?]}]
(let [theme (quo.theme/use-theme)
animation-distance messages.constants/header-animation-distance
props {:distance-from-list-top distance-from-list-top
:all-loaded? all-loaded?}
background-opacity (worklets/interpolate-navigation-view-opacity
(assoc props
:start-position
messages.constants/header-container-top-margin
:end-position
(+ animation-distance
messages.constants/header-container-top-margin)))
banner-opacity (worklets/interpolate-navigation-view-opacity
(assoc props
:start-position
(+ navigation-view-height
messages.constants/pinned-banner-animation-start-position)
:end-position
(+ animation-distance
navigation-view-height
messages.constants/pinned-banner-animation-start-position)))]
(defn header-background
[{:keys [chat-id navigation-view-height]}]
(let [theme (quo.theme/use-theme)]
[:<>
[reanimated/view {:style (style/animated-background-view background-opacity navigation-view-height)}
[rn/view {:style (style/background navigation-view-height)}
[quo/blur
{:style {:flex 1}
:blur-amount 20
@@ -121,58 +83,37 @@
:overlay-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur theme)
:blur-radius (if platform/ios? 20 10)}]]
[pin.banner/banner
{:chat-id chat-id
:banner-opacity banner-opacity
:top-offset navigation-view-height}]]))
{:chat-id chat-id
:top-offset navigation-view-height}]]))
(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)]
(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
:as chat} (rf/sub [:chats/current-chat-chat-view])
top-insets (safe-area/get-top)
navigation-view-height (+ top-insets messages.constants/top-bar-height)]
[rn/view {:style (style/navigation-view navigation-view-height)}
[header-background
{: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
{:icon-only? true
:type :grey
:background button-background
:size 32
:accessibility-label :back-button
:on-press #(rf/dispatch [:navigate-back])}
(if (= chat-type constants/community-chat-type) :i/arrow-left :i/close)]]
[header-content-container
{:chat chat
:distance-from-list-top distance-from-list-top
:all-loaded? all-loaded?
:chat-screen-layout-calculations-complete? chat-screen-layout-calculations-complete?}]
[reanimated/view {:style (style/button-animation-container navigation-buttons-opacity)}
[quo/button
{:icon-only? true
:type :grey
:background button-background
:size 32
:accessibility-label :options-button
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [actions/chat-actions chat true])}]))}
:i/options]]]]))
:navigation-view-height navigation-view-height}]
[rn/view {:style (style/header-container top-insets)}
[quo/button
{:icon-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :back-button
:on-press #(rf/dispatch [:navigate-back])}
(if (= chat-type constants/community-chat-type) :i/arrow-left :i/close)]
[header-content-container chat]
[quo/button
{:icon-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :options-button
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [actions/chat-actions chat true])}]))}
:i/options]]]))
@@ -1,6 +1,5 @@
(ns status-im.contexts.chat.messenger.messages.pin.banner.style
(:require
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
(def container
@@ -10,8 +9,6 @@
:right 0
:height messages.constants/pinned-banner-height})
(defn container-animated-style
[top-offset banner-opacity]
(reanimated/apply-animations-to-style
{:opacity banner-opacity}
(assoc container :top top-offset)))
(defn container-with-top-offset
[top-offset]
(assoc container :top top-offset))
@@ -2,30 +2,26 @@
(:require
[quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.pin.banner.style :as style]
[utils.re-frame :as rf]))
(defn f-banner
[{:keys [chat-id banner-opacity top-offset]} latest-pin-text pins-count]
(let [theme (quo.theme/use-theme)]
[reanimated/view {:style (style/container-animated-style top-offset banner-opacity)}
[quo/blur
{:style style/container
:blur-radius (if platform/ios? 20 10)
:blur-type theme
:blur-amount 20}]
[quo/banner
{:latest-pin-text latest-pin-text
:pins-count pins-count
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}]]))
(defn banner
[{:keys [chat-id] :as props}]
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
[{:keys [chat-id top-offset]}]
(let [theme (quo.theme/use-theme)
latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
pins-count (rf/sub [:chats/pin-messages-count chat-id])]
(when (> pins-count 0)
[:f> f-banner props latest-pin-text pins-count])))
[rn/view {:style (style/container-with-top-offset top-offset)}
[quo/blur
{:style style/container
:blur-radius (if platform/ios? 20 10)
:blur-type theme
:blur-amount 20}]
[quo/banner
{:latest-pin-text latest-pin-text
:pins-count pins-count
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}]])))
@@ -7,7 +7,7 @@
[utils.worklets.chat.messenger.composer :as worklets]))
(defn button
[{:keys [chat-list-scroll-y]}]
[chat-list-scroll-y]
(let [{window-height :height} (rn/get-window)
scroll-down-button-opacity (worklets/scroll-down-button-opacity
chat-list-scroll-y
@@ -19,57 +19,50 @@
[utils.re-frame :as rf]))
(defn- footer
[props]
[layout-height]
(let [current-chat-id (rf/sub [:chats/current-chat-id])
able-to-send-message? (rf/sub [:chats/able-to-send-message?])]
(when-not (string/blank? current-chat-id)
(if able-to-send-message?
[composer/view props]
[composer/view layout-height]
[contact-requests.bottom-drawer/view {:contact-id current-chat-id}]))))
(defn- chat-screen
[{:keys [insets] :as props}]
[on-layout-done?]
(let [theme (quo.theme/use-theme)
layout-height (rn/use-ref-atom 0)
distance-from-list-top (reanimated/use-shared-value 0)
chat-list-scroll-y (reanimated/use-shared-value 0)
alert-banners-top-margin (rf/sub [:alert-banners/top-margin])
chat-exist? (rf/sub [:chats/current-chat-exist?])]
(when chat-exist?
[rn/keyboard-avoiding-view
{:style (style/keyboard-avoiding-container theme)
:keyboard-vertical-offset (- (if platform/ios? alert-banners-top-margin 0) (:bottom insets))}
[:<>
[list.view/messages-list-content props]
[scroll-to-bottom/button props]]
[messages.navigation/view props]
[footer props]])))
(defn lazy-chat-screen
[chat-screen-layout-calculations-complete? *screen-loaded?*]
(let [screen-loaded? (rf/sub [:shell/chat-screen-loaded?])
distance-from-list-top (reanimated/use-shared-value 0)
chat-list-scroll-y (reanimated/use-shared-value 0)
props {:insets (safe-area/get-insets)
:content-height (atom 0)
:layout-height (atom 0)
:distance-atom (atom 0)
:distance-from-list-top distance-from-list-top
:chat-list-scroll-y chat-list-scroll-y
:chat-screen-layout-calculations-complete?
chat-screen-layout-calculations-complete?}]
(when *screen-loaded?*
(rn/use-mount #(reset! *screen-loaded?* true)))
(when-not (if *screen-loaded?* @*screen-loaded?* screen-loaded?)
(reanimated/set-shared-value chat-screen-layout-calculations-complete? false)
(reanimated/set-shared-value distance-from-list-top 0)
(reanimated/set-shared-value chat-list-scroll-y 0))
(when (if *screen-loaded?* @*screen-loaded?* screen-loaded?)
[chat-screen props])))
insets (safe-area/get-insets)]
[rn/keyboard-avoiding-view
{:style (style/keyboard-avoiding-container theme)
:keyboard-vertical-offset (- (if platform/ios? alert-banners-top-margin 0)
(:bottom insets))}
[:<>
[list.view/messages-list-content
{:insets insets
:distance-from-list-top distance-from-list-top
:chat-list-scroll-y chat-list-scroll-y
:layout-height layout-height
:on-layout-done? on-layout-done?}]
[scroll-to-bottom/button chat-list-scroll-y]]
[messages.navigation/view]
[footer layout-height]]))
(defn chat
[]
(let [chat-screen-layout-calculations-complete? (reanimated/use-shared-value false)
jump-to-enabled? (ff/enabled? ::ff/shell.jump-to)
*screen-loaded?* (when-not jump-to-enabled?
(reagent/atom false))]
[:<>
[lazy-chat-screen chat-screen-layout-calculations-complete? *screen-loaded?*]
[placeholder.view/view chat-screen-layout-calculations-complete?]]))
(let [on-layout-done? (reagent/atom false)
first-render-done? (reagent/atom false)]
(fn []
(let [chat-exists? (rf/sub [:chats/current-chat-exist?])
jump-to-enabled? (ff/enabled? ::ff/shell.jump-to)
screen-loaded-for-jump-to? (rf/sub [:shell/chat-screen-loaded?])
screen-loaded? (if jump-to-enabled?
screen-loaded-for-jump-to?
@first-render-done?)]
(rn/use-mount #(reset! first-render-done? true))
[:<>
(when (and chat-exists? screen-loaded?)
[chat-screen on-layout-done?])
[placeholder.view/view on-layout-done?]]))))
@@ -1,16 +1,15 @@
(ns status-im.contexts.chat.messenger.placeholder.style
(:require [quo.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
[react-native.safe-area :as safe-area]))
(defn container
[top opacity z-index theme]
(reanimated/apply-animations-to-style
{:opacity opacity
:z-index z-index}
{:position :absolute
:padding-top top
:top 0
:left 0
:right 0
:bottom 0
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}))
[theme on-layout-done?]
{:position :absolute
:padding-top (safe-area/get-top)
:top 0
:left 0
:right 0
:bottom 0
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:opacity (if on-layout-done? 0 1)
:z-index (if on-layout-done? 0 2)})
@@ -3,11 +3,8 @@
[quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.contexts.chat.messenger.placeholder.style :as style]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.placeholder :as worklets]))
[utils.re-frame :as rf]))
(defn- loading-skeleton
[]
@@ -17,13 +14,10 @@
:animated? false}])
(defn view
[chat-screen-layout-calculations-complete?]
[on-layout-done?]
(let [theme (quo.theme/use-theme)
top (safe-area/get-top)
chat-exist? (rf/sub [:chats/current-chat-exist?])
opacity (worklets/placeholder-opacity chat-screen-layout-calculations-complete?)
z-index (worklets/placeholder-z-index chat-screen-layout-calculations-complete?)]
[reanimated/view {:style (style/container top opacity z-index theme)}
chat-exist? (rf/sub [:chats/current-chat-exist?])]
[rn/view {:style (style/container theme @on-layout-done?)}
(when-not chat-exist?
[quo/page-nav
{:icon-name :i/arrow-left
@@ -119,38 +119,29 @@
:icon-left :i/communities}
(i18n/label :t/request-to-join)])
(defn- info-button-handler
(defn token-gated-communities-info
[]
(rf/dispatch
[:show-bottom-sheet
{:content
(fn []
[quo/documentation-drawers
{:title (i18n/label :t/token-gated-communities)
:show-button? true
:button-label (i18n/label :t/read-more)
:button-icon :info}
[quo/text {:size :paragraph-2}
(i18n/label :t/token-gated-communities-info)]])}]))
[quo/documentation-drawers {:title (i18n/label :t/token-gated-communities)}
[quo/text {:size :paragraph-2}
(i18n/label :t/token-gated-communities-info)]])
(defn- token-requirements
[{:keys [id color role-permissions?]}]
(let [{:keys [can-request-access?
no-member-permission?
tokens
networks-not-supported?
highest-permission-role]} (rf/sub [:community/token-gated-overview id])
highest-role-text
(i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))
on-press (rn/use-callback (fn []
(if config/community-accounts-selection-enabled?
(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
(rf/dispatch [:open-modal :community-requests-to-join
{:id
id}])))
[id])]
(let [{:keys [can-request-access? no-member-permission? networks-not-supported?
highest-permission-role
tokens]} (rf/sub [:community/token-gated-overview id])
highest-role-text (i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))
on-press (rn/use-callback
(fn []
(if config/community-accounts-selection-enabled?
(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
(rf/dispatch [:open-modal :community-requests-to-join
{:id id}])))
[id])
on-press-info #(rf/dispatch
[:show-bottom-sheet {:content token-gated-communities-info}])]
(cond
networks-not-supported?
[network-not-supported]
@@ -160,13 +151,12 @@
:else
[quo/community-token-gating
{:role highest-role-text
:tokens tokens
{:role highest-role-text
:tokens tokens
:community-color color
:satisfied? can-request-access?
:on-press on-press
:on-press-info
info-button-handler}])))
:satisfied? can-request-access?
:on-press on-press
:on-press-info on-press-info}])))
(defn- join-community
[{:keys [id joined permissions] :as community}]
@@ -0,0 +1,36 @@
(ns status-im.contexts.keycard.check.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/check-keycard)
:description :text
:description-text (i18n/label :t/see-keycard-ready)}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :check-your-keycard)}]]
[quo/divider-label (i18n/label :t/tips-scan-keycard)]
[rn/view {:style {:padding-horizontal 10}}
[quo/markdown-list
{:container-style {:padding-vertical 10}
:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list
{:container-style {:padding-bottom 25}
:description (i18n/label :t/keep-card-steady)}]]
[quo/button
{:on-press #(rf/dispatch [:keycard/connect])
:container-style {:margin-horizontal 20}}
(i18n/label :t/ready-to-scan)]])
+8 -1
View File
@@ -10,10 +10,15 @@
(defonce ^:private active-listeners (atom []))
(defn register-card-events
(defn unregister-card-events
[]
(doseq [listener @active-listeners]
(keycard/remove-event-listener listener))
(reset! active-listeners nil))
(defn register-card-events
[]
(unregister-card-events)
(reset! active-listeners
[(keycard/on-card-connected #(rf/dispatch [:keycard/on-card-connected]))
(keycard/on-card-disconnected #(rf/dispatch [:keycard/on-card-disconnected]))
@@ -23,7 +28,9 @@
(keycard/on-nfc-timeout #(rf/dispatch [:keycard.ios/on-nfc-timeout])))
(keycard/on-nfc-enabled #(rf/dispatch [:keycard/on-check-nfc-enabled-success true]))
(keycard/on-nfc-disabled #(rf/dispatch [:keycard/on-check-nfc-enabled-success false]))]))
(rf/reg-fx :effects.keycard/register-card-events register-card-events)
(rf/reg-fx :effects.keycard/unregister-card-events unregister-card-events)
(defn check-nfc-enabled
[]
@@ -0,0 +1,36 @@
(ns status-im.contexts.keycard.empty.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[status-im.contexts.keycard.sheets.migrate.view :as sheets.migrate]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/keycard-empty)
:description :text
:description-text (i18n/label :t/what-to-do)}]
[rn/view {:style {:padding-horizontal 28 :padding-top 20}}
[quo/small-option-card
{:variant :main
:title (i18n/label :t/import-key-pair-keycard)
:subtitle (i18n/label :t/use-keycard-login-sign)
:button-label (i18n/label :t/import-profile-key-pair)
:accessibility-label :get-keycard
:image (resources/get-image :generate-keys)
:on-press #(rf/dispatch [:show-bottom-sheet
{:theme :dark
:content (fn [] [sheets.migrate/view])}])}]]
[quo/information-box
{:type :default
:style {:margin-top 32 :margin-horizontal 28}}
(i18n/label :t/empty-card-info)]])
@@ -0,0 +1,42 @@
(ns status-im.contexts.keycard.error.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[utils.re-frame :as rf]))
(def titles
{:keycard/error.keycard-wrong {:title "Keycard is not empty"
:description "You cant use it to store new keys right now"}
:keycard/error.keycard-unpaired {:title "Keycard is full"
:description "All pairing slots are occupied"}
:keycard/error.keycard-frozen {:title "Keycard is locked"
:description "You cant use it right now"}
:keycard/error.keycard-locked {:title "Keycard is locked"
:description "You cant use it right now"}})
(defn view
[]
(let [{:keys [top bottom]} (safe-area/get-insets)
error (rf/sub [:keycard/application-info-error])
{:keys [title description]} (get titles error)]
[quo/overlay
{:type :shell
:container-style {:padding-top top
:padding-bottom bottom}}
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title title
:description :text
:description-text description}]
[rn/view {:height 226}]
[rn/view {:padding-horizontal 20}
[quo/info-message
{:container-style {:padding-top 15}
:icon :i/info
:size :default}
"To unlock or factory reset the Keycard, please use the Status desktop app. If you'd like this features on mobile, feel free to upvote them and discuss in the Status community."]]]))
+53 -11
View File
@@ -1,8 +1,8 @@
(ns status-im.contexts.keycard.events
(:require [re-frame.core :as rf]
status-im.contexts.keycard.login.events
status-im.contexts.keycard.nfc-sheet.events
status-im.contexts.keycard.pin.events
status-im.contexts.keycard.sheet.events
status-im.contexts.keycard.sign.events
[status-im.contexts.keycard.utils :as keycard.utils]
[taoensso.timbre :as log]))
@@ -14,7 +14,9 @@
(rf/reg-event-fx :keycard.ios/on-nfc-user-cancelled
(fn [{:keys [db]}]
(log/debug "[keycard] nfc user cancelled")
{:db (assoc-in db [:keycard :pin :status] nil)
{:db (-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))
:fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])]
[:dispatch on-nfc-cancelled-event-vector])]}))
@@ -42,12 +44,6 @@
{:db (assoc-in db [:keycard :card-connected?] false)
:fx [[:dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]]]}))
(rf/reg-event-fx :keycard/get-application-info
(fn [_ [{:keys [on-success on-failure]}]]
(log/debug "[keycard] get-application-info")
{:effects.keycard/get-application-info {:on-success on-success
:on-failure on-failure}}))
(rf/reg-event-fx :keycard/on-retrieve-pairings-success
(fn [{:keys [db]} [pairings]]
{:db (assoc-in db [:keycard :pairings] pairings)
@@ -60,7 +56,7 @@
(rf/reg-event-fx :keycard/on-action-with-pin-error
(fn [{:keys [db]} [error]]
(log/debug "[keycard] get keys error: " error)
(log/debug "[keycard] on-action-with-pin-error: " error)
(let [tag-was-lost? (keycard.utils/tag-lost? (:error error))
pin-retries-count (keycard.utils/pin-retries (:error error))]
(if tag-was-lost?
@@ -69,7 +65,53 @@
{:effects.utils/show-popup {:title "wrong-keycard"}}
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count)
(update-in [:keycard :pin] assoc :status :error))
:fx [[:dispatch [:keycard/hide-connection-sheet]]
(assoc-in [:keycard :pin :status] :error))
:fx [[:dispatch [:keycard/disconnect]]
(when (zero? pin-retries-count)
[:effects.utils/show-popup {:title "frozen-keycard"}])]})))))
(rf/reg-event-fx :keycard/on-get-application-info-success
(fn [{:keys [db]} [application-info {:keys [key-uid on-success-fx]}]]
(let [error (keycard.utils/validate-application-info key-uid application-info)]
(if error
(case error
:keycard/error.not-keycard
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.not-keycard]]]}
:keycard/error.keycard-blank
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.empty]]]}
{:db (assoc-in db [:keycard :application-info-error] error)
:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.error]]]})
{:db (-> db
(assoc-in [:keycard :application-info] application-info)
(assoc-in [:keycard :pin :status] :verifying))
:fx on-success-fx}))))
(rf/reg-event-fx :keycard/get-application-info
(fn [_ [{:keys [on-success on-failure]}]]
{:effects.keycard/get-application-info {:on-success on-success :on-failure on-failure}}))
(rf/reg-event-fx :keycard/cancel-connection
(fn [{:keys [db]}]
{:db (-> db
(assoc-in [:keycard :on-card-connected-event-vector] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))}))
(rf/reg-event-fx :keycard/disconnect
(fn [_ _]
{:fx [[:dispatch [:keycard/cancel-connection]]
[:dispatch [:keycard/hide-connection-sheet]]]}))
(rf/reg-event-fx :keycard/connect
(fn [{:keys [db]} [args]]
(let [connected? (get-in db [:keycard :card-connected?])
event-vector [:keycard/get-application-info
{:on-success #(rf/dispatch [:keycard/on-get-application-info-success % args])}]]
{:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector)
:fx [[:dispatch
[:keycard/show-connection-sheet
{:on-cancel-event-vector [:keycard/cancel-connection]}]]
(when connected?
[:dispatch event-vector])]})))
@@ -1,7 +1,5 @@
(ns status-im.contexts.keycard.login.events
(:require [status-im.contexts.keycard.utils :as keycard.utils]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(:require [utils.re-frame :as rf]))
(rf/reg-event-fx :keycard.login/on-get-keys-success
(fn [{:keys [db]} [data]]
@@ -16,7 +14,7 @@
:password encryption-public-key
:key-uid key-uid
:name (:name profile)))
:fx [[:dispatch [:keycard/hide-connection-sheet]]
:fx [[:dispatch [:keycard/disconnect]]
[:effects.keycard/login-with-keycard
{:password encryption-public-key
:whisper-private-key whisper-private-key
@@ -33,36 +31,8 @@
:password encryption-public-key
:key-uid key-uid
:name (:name profile)))
:fx [[:dispatch [:keycard/hide-connection-sheet]]
:fx [[:dispatch [:keycard/disconnect]]
[:effects.keycard/login-with-keycard
{:password encryption-public-key
:whisper-private-key whisper-private-key
:key-uid key-uid}]]}))))
(rf/reg-event-fx :keycard.login/on-get-application-info-success
(fn [{:keys [db]} [application-info {:keys [key-uid on-read-fx]}]]
(let [error (keycard.utils/validate-application-info key-uid application-info)]
(if error
{:effects.utils/show-popup {:title (str error)}}
{:db (-> db
(assoc-in [:keycard :application-info] application-info)
(assoc-in [:keycard :pin :status] :verifying))
:fx on-read-fx}))))
(rf/reg-event-fx :keycard.login/cancel-reading-card
(fn [{:keys [db]}]
{:db (assoc-in db [:keycard :on-card-connected-event-vector] nil)}))
(rf/reg-event-fx :keycard/read-card
(fn [{:keys [db]} [args]]
(let [connected? (get-in db [:keycard :card-connected?])
event-vector [:keycard/get-application-info
{:on-success #(rf/dispatch [:keycard.login/on-get-application-info-success %
args])}]]
(log/debug "[keycard] proceed-to-login")
{:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector)
:fx [[:dispatch
[:keycard/show-connection-sheet
{:on-cancel-event-vector [:keycard.login/cancel-reading-card]}]]
(when connected?
[:dispatch event-vector])]})))
@@ -1,18 +1,16 @@
(ns status-im.contexts.keycard.sheet.events
(ns status-im.contexts.keycard.nfc-sheet.events
(:require [re-frame.core :as rf]
[react-native.platform :as platform]
[taoensso.timbre :as log]))
(rf/reg-event-fx :keycard/show-connection-sheet-component
(fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]]
{:db (assoc-in db [:keycard :connection-sheet-opts] {:on-close #(rf/dispatch on-cancel-event-vector)})
:fx [[:dismiss-keyboard true]
[:show-nfc-sheet nil]]}))
(rf/reg-event-fx :keycard/show-connection-sheet
(fn [_ [args]]
(fn [{:keys [db]} [{:keys [on-cancel-event-vector]} :as args]]
(if platform/android?
{:dispatch [:keycard/show-connection-sheet-component args]}
{:db (assoc-in db
[:keycard :connection-sheet-opts]
{:on-close #(rf/dispatch on-cancel-event-vector)})
:fx [[:dismiss-keyboard true]
[:show-nfc-sheet nil]]}
{:effects.keycard.ios/start-nfc
{:on-success
(fn []
@@ -1,8 +1,9 @@
(ns status-im.contexts.keycard.sheet.view
(ns status-im.contexts.keycard.nfc-sheet.view
(:require [quo.foundations.colors :as colors]
quo.theme
[react-native.core :as rn]
[status-im.common.resources :as resources]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn connect-keycard
@@ -18,13 +19,13 @@
:padding-vertical 30
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}}
[rn/text {:style {:font-size 26 :color "#9F9FA5" :margin-bottom 36}}
"Ready to Scan"]
(i18n/label :t/ready-to-scan)]
[rn/image
{:source (resources/get-image :nfc-prompt)}]
[rn/text {:style {:font-size 16 :color :white :margin-vertical 36}}
(if connected?
"Connected. Dont move your card."
"Hold your phone near a Status Keycard")]
(i18n/label :t/connected-dont-move)
(i18n/label :t/hold-phone-near-keycard))]
[rn/pressable
{:on-press (fn []
(when on-close (on-close))
@@ -33,4 +34,4 @@
[rn/view
{:style {:background-color "#8E8E93" :flex 1 :align-items :center :padding 18 :border-radius 10}}
[rn/text {:style {:color :white :font-size 16}}
"Cancel"]]]]]))
(i18n/label :t/cancel)]]]]]))
@@ -0,0 +1,28 @@
(ns status-im.contexts.keycard.not-keycard.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [{:keys [top bottom]} (safe-area/get-insets)]
[quo/overlay
{:type :shell
:container-style {:padding-top top
:padding-bottom bottom}}
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/oops-not-keycard)
:description :text
:description-text (i18n/label :t/make-sure-keycard)}]
[rn/view {:flex 1}]
[rn/view {:padding-horizontal 20}
[quo/button {:on-press #(rf/dispatch [:keycard/connect])}
(i18n/label :t/try-again)]]]))
@@ -0,0 +1,33 @@
(ns status-im.contexts.keycard.sheets.migrate.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/drawer-top
{:type :context-tag
:context-tag-type :default
:title (i18n/label :t/migrate-key-pair-keycard)
:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}]
[rn/view {:style {:padding-horizontal 20}}
[quo/text {}
(i18n/label :t/migrate-key-pair-keycard-default-key {:name profile-name})]
[quo/information-box
{:type :default
:style {:margin-top 20 :margin-bottom 12}}
(i18n/label :t/migrate-key-pair-keycard-info)]]
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/continue)
:button-one-props {:on-press #()}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))
+14 -13
View File
@@ -14,16 +14,17 @@
path (get-in db [:wallet :accounts current-address :path])
key-uid (get-in db [:profile/profile :key-uid])]
{:fx [[:dispatch
[:keycard/read-card
{:key-uid key-uid
:on-read-fx [[:effects.keycard/sign
{:pin pin-text
:path path
:hash (utils.address/naked-address tx-hash)
:on-success
#(do
(rf/dispatch [:keycard/hide-connection-sheet])
(rf/dispatch
[:wallet/proceed-with-transactions-signatures
(get-signature-map tx-hash %)]))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]}]]]})))
[:keycard/connect
{:key-uid key-uid
:on-success-fx [[:effects.keycard/sign
{:pin pin-text
:path path
:hash (utils.address/naked-address tx-hash)
:on-success
#(do
(rf/dispatch [:keycard/disconnect])
(rf/dispatch
[:wallet/proceed-with-transactions-signatures
(get-signature-map tx-hash %)]))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]]}]]]})))
+9 -6
View File
@@ -18,28 +18,31 @@
(defn validate-application-info
[profile-key-uid {:keys [key-uid paired? pin-retry-counter puk-retry-counter] :as application-info}]
(let [profile-mismatch? (or (nil? profile-key-uid) (not= profile-key-uid key-uid))]
(log/debug "[keycard] login-with-keycard"
(log/debug "[keycard]" "login-with-keycard"
"empty application info" (empty? application-info)
"no key-uid" (empty? key-uid)
"profile-mismatch?" profile-mismatch?
"no pairing" paired?)
(cond
(empty? application-info)
:not-keycard
:keycard/error.not-keycard
(empty? (:key-uid application-info))
:keycard-blank
:keycard/error.keycard-blank
profile-mismatch?
:keycard-wrong
:keycard/error.keycard-wrong
(not paired?)
:keycard-unpaired
:keycard/error.keycard-unpaired
(and (zero? pin-retry-counter)
(or (nil? puk-retry-counter)
(pos? puk-retry-counter)))
nil
:keycard/error.keycard-frozen
(zero? puk-retry-counter)
:keycard/error.keycard-locked
:else
nil)))
@@ -58,7 +58,7 @@
(reset! shell.state/screen-height height)
(async-storage/set-item! :screen-height height))))
(defn f-view
(defn view
[dark-overlay?]
(let [view-id (rf/sub [:view-id])
animate? (not dark-overlay?)
@@ -94,5 +94,3 @@
:blur-radius (if platform/android? 25 10)
:blur-type :transparent
:overlay-color :transparent}])]))
(defn view [dark-overlay?] [:f> f-view dark-overlay?])
@@ -242,12 +242,13 @@
{:on-complete
(fn [pin-text]
(rf/dispatch
[:keycard/read-card
{:key-uid key-uid
:on-read-fx [[:effects.keycard/get-keys
{:pin pin-text
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]}]))}]
[:keycard/connect
{:key-uid key-uid
:on-success-fx [[:effects.keycard/get-keys
{:pin pin-text
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]]}]))}]
[password-input])]
(when-not keycard-pairing
[quo/button
@@ -1,6 +1,7 @@
(ns status-im.contexts.profile.settings.list-items
(:require [status-im.common.not-implemented :as not-implemented]
[status-im.config :as config]
[status-im.feature-flags :as ff]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -54,9 +55,9 @@
:image :icon
:blur? true
:action :arrow})
(when config/show-not-implemented-features?
(when (ff/enabled? ::ff/keycard.migrate-profile)
{:title (i18n/label :t/keycard)
:on-press not-implemented/alert
:on-press #(rf/dispatch [:open-modal :screen/settings.keycard])
:image-props :i/keycard
:image :icon
:blur? true
@@ -0,0 +1,45 @@
(ns status-im.contexts.settings.keycard.view
(:require [quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [keycard-profile? (rf/sub [:keycard/keycard-profile?])]
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/keycard)}]
(if keycard-profile?
[:<>]
[rn/view {:style {:padding-horizontal 28 :padding-top 20}}
[quo/small-option-card
{:variant :main
:title (i18n/label :t/get-keycard)
:subtitle (i18n/label :t/secure-wallet-card)
:button-label (i18n/label :t/buy-keycard)
:accessibility-label :get-keycard
:image (resources/get-image :generate-keys)
:on-press #()}]
[rn/view {:style {:margin-top 24}}
[quo/text
{:style {:margin-bottom 12
:color colors/white-opa-70}
:size :paragraph-2
:weight :medium}
(i18n/label :t/own-keycard)]]
[quo/small-option-card
{:variant :icon
:title (i18n/label :t/setup-keycard)
:subtitle (i18n/label :t/ready-keycard)
:accessibility-label :setup-keycard
:image (resources/get-image :use-keycard)
:on-press #(rf/dispatch [:open-modal :screen/keycard.check])}]])]))
@@ -28,7 +28,8 @@
;; TODO: Replace with correct enum values once status-go implements them.
(def ^:const tx 66612)
(def ^:const system 66614)
(def ^:const system
#{new-installation-received new-installation-created})
(def ^:const membership
"Membership is like a logical group of notifications with different types, i.e.
@@ -0,0 +1,72 @@
(ns status-im.contexts.wallet.buy-crypto.events
(:require [re-frame.core :as rf]
[react-native.core :as rn]
[status-im.contexts.wallet.sheets.buy-network-selection.view :as buy-network-selection]
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
[utils.i18n :as i18n]))
(rf/reg-event-fx :wallet.buy-crypto/select-provider
(fn [{:keys [db]} [{:keys [account provider recurrent?]}]]
(let [swap-network (get-in db [:wallet :ui :swap :network])]
{:db (-> db
(assoc-in [:wallet :ui :buy-crypto :account] account)
(assoc-in [:wallet :ui :buy-crypto :provider] provider)
(assoc-in [:wallet :ui :buy-crypto :recurrent?] recurrent?))
:fx [(if swap-network
[:dispatch [:wallet.buy-crypto/select-network swap-network]]
[:dispatch
[:show-bottom-sheet
{:content
(fn []
[buy-network-selection/view
{:provider provider
:account account}])}]])]})))
(rf/reg-event-fx :wallet.buy-crypto/select-network
(fn [{:keys [db]} [network]]
(let [provider (get-in db [:wallet :ui :buy-crypto :provider])
network-chain-id (:chain-id network)
supported-tokens (:supported-tokens provider)
supported-tokens-on-network (set (map :symbol
(filter (fn [token]
(= (:chain-id token) network-chain-id))
supported-tokens)))]
{:db (assoc-in db [:wallet :ui :buy-crypto :network] network)
:fx [[:dispatch
[:show-bottom-sheet
{:content
(fn []
[select-asset/view
{:title (i18n/label :t/select-asset-to-buy)
:network network
:provider provider
:on-select (fn [token]
(rf/dispatch [:wallet/get-crypto-on-ramp-url {:token token}]))
:hide-token-fn (fn [_ token]
(not (contains? supported-tokens-on-network
(:symbol token))))}])}]]]})))
(rf/reg-event-fx
:wallet/get-crypto-on-ramp-url
(fn [{:keys [db]} [{:keys [token]}]]
(let [account (get-in db [:wallet :ui :buy-crypto :account])
provider (get-in db [:wallet :ui :buy-crypto :provider])
network (get-in db [:wallet :ui :buy-crypto :network])
recurrent? (get-in db [:wallet :ui :buy-crypto :recurrent?])
provider-id (:id provider)
parameters {:symbol (:symbol token)
:destAddress (:address account)
:chainID (:chain-id network)
:isRecurrent recurrent?}]
{:fx [[:json-rpc/call
[{:method "wallet_getCryptoOnRampURL"
:params [provider-id parameters]
:on-success (fn [url]
(rf/dispatch [:navigate-back])
(rf/dispatch [:wallet.buy-crypto/clean-all])
(rn/open-url url))
:on-error [:wallet/log-rpc-error {:event :wallet/get-crypto-on-ramps}]}]]]})))
(rf/reg-event-fx :wallet.buy-crypto/clean-all
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui] dissoc :buy-crypto)}))
@@ -8,19 +8,20 @@
[utils.re-frame :as rf]))
(defn get-bottom-sheet-args
[switcher-type]
[switcher-type params]
(case switcher-type
:account-options {:content account-options/view
:hide-handle? true}
:select-account {:content select-account/view}
:select-account {:content (fn []
[select-account/view params])}
nil))
(defn- on-dapps-press
[switcher-type]
(rf/dispatch [:show-bottom-sheet (get-bottom-sheet-args switcher-type)]))
[switcher-type params]
(rf/dispatch [:show-bottom-sheet (get-bottom-sheet-args switcher-type params)]))
(defn view
[{:keys [type on-press accessibility-label icon-name switcher-type margin-top]
[{:keys [type on-press accessibility-label icon-name switcher-type margin-top params]
:or {icon-name :i/close
accessibility-label :top-bar
switcher-type :account-options
@@ -45,6 +46,6 @@
(when-not sending-collectible?
{:content-type :account-switcher
:customization-color color
:on-press #(on-dapps-press switcher-type)
:on-press #(on-dapps-press switcher-type params)
:emoji emoji
:type (when watch-only? :watch-only)})]}]))
@@ -32,9 +32,11 @@
:selected)}]))
(defn view
[{:keys [content-container-style search-text on-token-press preselected-token-symbol]
[{:keys [content-container-style search-text on-token-press preselected-token-symbol chain-ids]
:or {content-container-style {:padding-horizontal 8}}}]
(let [filtered-tokens (rf/sub [:wallet/current-viewing-account-tokens-filtered {:query search-text}])
(let [filtered-tokens (rf/sub [:wallet/current-viewing-account-tokens-filtered
{:query search-text
:chain-ids chain-ids}])
currency (rf/sub [:profile/currency])
currency-symbol (rf/sub [:profile/currency-symbol])]
[gesture/flat-list
@@ -47,7 +47,7 @@
(rf/dispatch [:wallet/bridge-select-token params]))})
(defn- action-swap
[{:keys [token token-symbol testnet-mode?]}]
[{:keys [token asset-to-receive token-symbol testnet-mode?]}]
{:icon :i/swap
:accessibility-label :swap
:label (i18n/label :t/swap)
@@ -56,6 +56,7 @@
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:wallet.swap/start
{:asset-to-pay (or token {:symbol token-symbol})
:asset-to-receive asset-to-receive
:open-new-screen? true}]))})
(defn- action-manage-tokens
@@ -75,23 +76,26 @@
(defn token-value-drawer
[token watch-only? entry-point]
(let [token-symbol (:token token)
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
{:query token-symbol}]))
selected-account (rf/sub [:wallet/current-viewing-account-address])
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
testnet-mode? (rf/sub [:profile/test-networks-enabled?])
params (cond-> {:start-flow? true
:owners token-owners
:testnet-mode? testnet-mode?}
selected-account
(assoc :token token-data
:stack-id :screen/wallet.accounts
:has-balance? (-> (get-in token [:values :fiat-unformatted-value])
(money/greater-than (money/bignumber "0"))))
(not selected-account)
(assoc :token-symbol token-symbol
:stack-id :wallet-stack))]
(let [token-symbol (:token token)
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
{:query token-symbol}]))
selected-account (rf/sub [:wallet/current-viewing-account-address])
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
testnet-mode? (rf/sub [:profile/test-networks-enabled?])
receive-token-symbol (if (= token-symbol "SNT") "ETH" "SNT")
asset-to-receive (rf/sub [:wallet/token-by-symbol receive-token-symbol])
params (cond-> {:start-flow? true
:owners token-owners
:testnet-mode? testnet-mode?
:asset-to-receive asset-to-receive}
selected-account
(assoc :token token-data
:stack-id :screen/wallet.accounts
:has-balance? (-> (get-in token [:values :fiat-unformatted-value])
(money/greater-than (money/bignumber "0"))))
(not selected-account)
(assoc :token-symbol token-symbol
:stack-id :wallet-stack))]
[quo/action-drawer
[(cond->> [(when (ff/enabled? ::ff/wallet.assets-modal-manage-tokens)
(action-manage-tokens watch-only?))
@@ -15,13 +15,17 @@
[full-name]
(first (string/split full-name #" ")))
(defn cut-fiat-balance-to-two-decimals
[balance]
(defn cut-fiat-balance
[balance decimals]
(let [valid-balance? (and balance
(or (number? balance) (.-toFixed balance)))]
(as-> balance $
(if valid-balance? $ 0)
(.toFixed $ 2))))
(.toFixed $ decimals))))
(defn cut-fiat-balance-to-two-decimals
[balance]
(cut-fiat-balance balance 2))
(defn prettify-balance
[currency-symbol balance]
@@ -151,6 +155,10 @@
[accounts address]
(some #(when (= (:address %) address) %) accounts))
(defn get-default-account
[accounts]
(some #(when (:default-account? %) %) accounts))
(defn calculate-token-fiat-value
"Returns the token fiat value for provided raw balance"
[{:keys [currency balance token]}]
@@ -1,6 +1,6 @@
(ns status-im.contexts.wallet.send.events-test
(:require
[cljs.test :refer-macros [is testing run-tests]]
[cljs.test :refer-macros [is testing]]
matcher-combinators.test
[re-frame.db :as rf-db]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
@@ -710,5 +710,3 @@
:stack-id stack-id
:start-flow? start-flow?
:netork-details network-details}])))))))
(run-tests)
@@ -158,16 +158,14 @@
active-screen? (= view-id current-screen-id)
bottom (safe-area/get-bottom)
[crypto-currency? set-crypto-currency] (rn/use-state initial-crypto-currency?)
on-navigate-back on-navigate-back
handle-on-confirm (fn [amount]
(rf/dispatch [:wallet/set-token-amount-to-send
{:amount amount
:stack-id current-screen-id}]))
{fiat-currency :currency} (rf/sub [:profile/profile])
{token-symbol :symbol
{token-symbol :symbol
token-networks :networks
:as
token} (rf/sub [:wallet/wallet-send-token])
:as token} (rf/sub [:wallet/wallet-send-token])
send-from-locked-amounts (rf/sub [:wallet/wallet-send-from-locked-amounts])
{:keys [total-balance]
:as token-by-symbol} (rf/sub [:wallet/token-by-symbol
@@ -196,10 +194,8 @@
(utils/cut-crypto-decimals-to-fit-usd-cents
token-balance
usd-conversion-rate)
(-> (money/crypto->fiat
token-balance
conversion-rate)
utils/cut-fiat-balance-to-two-decimals))
(utils/cut-fiat-balance-to-two-decimals
(money/crypto->fiat token-balance conversion-rate)))
input-value (controlled-input/input-value input-state)
valid-input? (not (or (controlled-input/empty-value? input-state)
(controlled-input/input-error input-state)))
@@ -389,7 +385,8 @@
:button-one-label (if should-try-again?
(i18n/label :t/try-again)
button-one-label)
:button-one-props (merge (when-not should-try-again? button-one-props)
:button-one-props (merge (when-not should-try-again?
button-one-props)
{:disabled? (or loading-routes?
(and (not should-try-again?) confirm-disabled?))
:on-press (cond
@@ -10,8 +10,7 @@
[input-amount/view
{:current-screen-id :screen/wallet.send-input-amount
:button-one-label (i18n/label :t/review-send)
:enabled-from-chain-ids (rf/sub
[:wallet/wallet-send-enabled-from-chain-ids])
:enabled-from-chain-ids (rf/sub [:wallet/wallet-send-enabled-from-chain-ids])
:from-enabled-networks (rf/sub [:wallet/wallet-send-enabled-networks])
:on-navigate-back (fn []
(rf/dispatch-sync [:wallet/stop-and-clean-suggested-routes])
@@ -0,0 +1,28 @@
(ns status-im.contexts.wallet.sheets.buy-network-selection.style)
(defn network-list-container
[mainnet?]
{:margin-horizontal 8
:padding-vertical (when mainnet? 8)})
(def header-container
{:height 62
:padding-horizontal 20})
(def context-tag
{:margin-top 0})
(def divider-label
{:padding-top 0
:padding-bottom 0})
(def subheader-container
{:flex-direction :row
:align-items :center
:margin-top 4})
(def on
{:margin-right 4})
(def to
{:margin-horizontal 4})
@@ -0,0 +1,78 @@
(ns status-im.contexts.wallet.sheets.buy-network-selection.view
(:require [quo.core :as quo]
[quo.foundations.resources :as quo.resources]
[react-native.core :as rn]
[status-im.constants :as constants]
[status-im.contexts.wallet.sheets.buy-network-selection.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- network-item
[{:keys [network on-select-network]}]
(let [{:keys [network-name]} network
mainnet? (= network-name constants/mainnet-network-name)]
[quo/network-list
{:label (name network-name)
:network-image (quo.resources/get-network network-name)
:on-press #(on-select-network network)
:container-style (style/network-list-container mainnet?)}]))
(defn- subheader
[{:keys [provider account]}]
[rn/view {:style style/subheader-container}
[quo/text
{:size :paragraph-2
:weight :medium
:style style/on}
(i18n/label :t/on-uppercase)]
[quo/context-tag
{:type :network
:network-logo (:logo-url provider)
:network-name (:name provider)
:size 24
:container-style style/context-tag}]
[quo/text
{:size :paragraph-2
:weight :medium
:style style/to}
(i18n/label :t/to)]
[quo/context-tag
{:type :account
:account-name (:name account)
:emoji (:emoji account)
:size 24
:container-style style/context-tag}]])
(defn view
[]
(let [provider (rf/sub [:wallet/wallet-buy-crypto-provider])
account (rf/sub [:wallet/wallet-buy-crypto-account])
network-details (rf/sub [:wallet/network-details])
mainnet-network (first network-details)
layer-2-networks (rest network-details)
render-fn (rn/use-callback (fn [network]
[network-item
{:network network
:on-select-network
(fn [network]
(rf/dispatch [:wallet.buy-crypto/select-network
network]))}]))]
[:<>
[rn/view {:style style/header-container}
[quo/text
{:size :heading-2
:weight :semi-bold}
(i18n/label :t/select-network-for-buying)]
[subheader
{:provider provider
:account account}]]
(when mainnet-network
[network-item
{:network mainnet-network
:on-select-network #(rf/dispatch [:wallet.buy-crypto/select-network mainnet-network])}])
[quo/divider-label {:container-style style/divider-label}
(i18n/label :t/layer-2)]
[rn/flat-list
{:data (vec layer-2-networks)
:render-fn render-fn
:scroll-enabled false}]]))
@@ -7,11 +7,20 @@
[utils.re-frame :as rf]))
(defn- crypto-on-ramp-item
[{:keys [name description fees logo-url site-url recurrent-site-url]} _ _ {:keys [tab]}]
(let [open-url (rn/use-callback (fn []
(rf/dispatch [:open-url
(if (= tab :recurrent) recurrent-site-url site-url)]))
[site-url recurrent-site-url tab])]
[{:keys [name description fees logo-url site-url recurrent-site-url urls-need-parameters]
:as provider}
_
_
{:keys [account tab]}]
(let [open-url (rn/use-callback
(fn []
(if urls-need-parameters
(rf/dispatch [:wallet.buy-crypto/select-provider
{:account account
:provider provider
:recurrent? (= tab :recurrent)}])
(rn/open-url (if (= tab :recurrent) recurrent-site-url site-url))))
[site-url recurrent-site-url tab])]
[quo/settings-item
{:title name
:description :text
@@ -39,6 +48,7 @@
(rn/use-mount (fn []
(rf/dispatch [:wallet/get-crypto-on-ramps])))
(let [crypto-on-ramps (rf/sub [:wallet/crypto-on-ramps])
account (rf/sub [:wallet/current-viewing-account-or-default])
[selected-tab set-selected-tab] (rn/use-state initial-tab)
[min-height set-min-height] (rn/use-state 0)
on-layout (rn/use-callback
@@ -58,5 +68,6 @@
(:one-time crypto-on-ramps))
:on-layout on-layout
:style (style/list-container min-height)
:render-data {:tab selected-tab}
:render-data {:tab selected-tab
:account account}
:render-fn crypto-on-ramp-item}]]))
@@ -2,30 +2,60 @@
(:require [quo.core :as quo]
quo.theme
[react-native.gesture :as gesture]
[status-im.contexts.wallet.common.utils :as wallet.utils]
[status-im.contexts.wallet.sheets.select-account.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- render-account-item
[{:keys [color address] :as account} _ _ {:keys [selected-account-address]}]
[quo/account-item
{:type :default
:account-props (assoc account :customization-color color)
:customization-color color
:state (if (= address selected-account-address) :selected :default)
:on-press (fn []
(rf/dispatch [:wallet/switch-current-viewing-account address])
(rf/dispatch [:hide-bottom-sheet]))}])
(defn list-item
[{:keys [account selected-account-address]}]
(let [{:keys [color address]} account]
[quo/account-item
{:type :default
:account-props (assoc account :customization-color color)
:customization-color color
:state (if (= address selected-account-address) :selected :default)
:on-press (fn []
(rf/dispatch [:wallet/switch-current-viewing-account address])
(rf/dispatch [:hide-bottom-sheet]))}]))
(defn list-item-with-balance
[{:keys [account selected-account-address asset-symbol network]}]
(let [{:keys [color address tokens]} account
token (->> tokens
(filter #(= (:symbol %) asset-symbol))
first)
token-balance (get-in token [:balances-per-chain (:chain-id network) :balance])]
[quo/account-item
{:type (if (= address selected-account-address) :default :tag)
:token-props {:symbol asset-symbol
:value (wallet.utils/cut-fiat-balance (or (js/parseFloat token-balance) 0)
4)}
:account-props (assoc account :customization-color color)
:customization-color color
:state (if (= address selected-account-address) :selected :default)
:on-press (fn []
(rf/dispatch [:wallet/switch-current-viewing-account address])
(rf/dispatch [:hide-bottom-sheet]))}]))
(defn view
[]
[{:keys [show-account-balances? asset-symbol network]}]
(let [selected-account-address (rf/sub [:wallet/current-viewing-account-address])
accounts (rf/sub [:wallet/operable-accounts])]
[:<>
[quo/drawer-top {:title (i18n/label :t/select-account)}]
[gesture/flat-list
{:data accounts
:render-fn render-account-item
:render-fn (fn [account _ _ {:keys [selected-account-address]}]
(if show-account-balances?
[list-item-with-balance
{:account account
:selected-account-address selected-account-address
:asset-symbol asset-symbol
:network network}]
[list-item
{:account account
:selected-account-address selected-account-address}]))
:render-data {:selected-account-address selected-account-address}
:content-container-style style/list-container
:shows-vertical-scroll-indicator false}]]))
@@ -51,14 +51,31 @@
:network-name full-name
:type :network}])
(defn- provider-view
[{:keys [name logo-url]}]
[quo/context-tag
{:size 24
:network-logo logo-url
:network-name name
:type :network}])
(defn- subheader-view
[{:keys [account network]}]
[{:keys [provider account network]}]
[rn/view {:style style/subheader-container}
[quo/text
{:size :paragraph-2
:weight :medium}
(i18n/label :t/select-asset-on)]
[account-view account]
(if provider
[provider-view provider]
[account-view account])
(when provider
[:<>
[quo/text
{:size :paragraph-2
:weight :medium}
(i18n/label :t/to)]
[account-view account]])
[quo/text
{:size :paragraph-2
:weight :medium}
@@ -66,9 +83,8 @@
[network-view network]])
(defn view
[{:keys [title on-select hide-token-fn disable-token-fn]}]
(let [account (rf/sub [:wallet/current-viewing-account])
{:keys [network]} (rf/sub [:wallet/swap])
[{:keys [title network provider on-select hide-token-fn disable-token-fn]}]
(let [account (rf/sub [:wallet/current-viewing-account-or-default])
[search-text set-search-text] (rn/use-state "")
window-height (:height (rn/get-window))]
[rn/safe-area-view {:style (style/container window-height)}
@@ -81,8 +97,9 @@
{:title title
:title-accessibility-label :title-label}]
[subheader-view
{:account account
:network network}]
{:account account
:network network
:provider provider}]
[assets-view
{:search-text search-text
:on-change-text #(set-search-text %)
+2 -14
View File
@@ -14,7 +14,7 @@
[utils.number :as number]))
(rf/reg-event-fx :wallet.swap/start
(fn [{:keys [db]} [{:keys [network open-new-screen?] :as data}]]
(fn [{:keys [db]} [{:keys [network asset-to-receive open-new-screen?] :as data}]]
(let [{:keys [wallet]} db
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
account (swap-utils/wallet-account wallet)
@@ -25,12 +25,6 @@
:account account
:test-networks-enabled? test-networks-enabled?
:token-symbol (get-in data [:asset-to-pay :symbol])}))
asset-to-receive (or (:asset-to-receive data)
(swap-utils/select-default-asset-to-receive
{:wallet wallet
:account account
:test-networks-enabled? test-networks-enabled?
:asset-to-pay asset-to-pay}))
network' (or network
(swap-utils/select-network asset-to-pay))]
{:db (-> db
@@ -76,11 +70,7 @@
(rf/reg-event-fx :wallet.swap/select-asset-to-receive
(fn [{:keys [db]} [{:keys [token]}]]
{:db (update-in db
[:wallet :ui :swap]
#(-> %
(assoc :asset-to-receive token)
(assoc :loading-swap-proposal? true)))}))
{:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)}))
(rf/reg-event-fx :wallet.swap/set-default-slippage
(fn [{:keys [db]}]
@@ -141,8 +131,6 @@
:amount amount
:amount-hex amount-in-hex
:loading-swap-proposal? true)
:always
(dissoc :error-response)
clean-approval-transaction?
(dissoc :approval-transaction-id :approved-amount :swap-proposal)))
:json-rpc/call [{:method "wallet_getSuggestedRoutesAsync"
@@ -20,10 +20,15 @@
(defn- assets-view
[search-text on-change-text]
(let [on-token-press (fn [token]
(rf/dispatch [:wallet.swap/start
{:asset-to-pay token
:open-new-screen? false}]))]
(let [snt-token (rf/sub [:wallet/token-by-symbol "SNT"])
eth-token (rf/sub [:wallet/token-by-symbol "ETH"])
on-token-press (fn [token]
(let [pay-token-symbol (:symbol token)
asset-to-receive (if (= pay-token-symbol "SNT") eth-token snt-token)]
(rf/dispatch [:wallet.swap/start
{:asset-to-pay token
:asset-to-receive asset-to-receive
:open-new-screen? false}])))]
[:<>
[search-input search-text on-change-text]
[asset-list/view
@@ -10,8 +10,12 @@
:height 36
:background-color :transparent})
(def floating-button-page-content-container
{:margin-bottom 168})
(def scroll-view-container
{:padding-bottom 150})
{:flex 1
:padding-bottom 20})
(def content-container
{:padding-top 12
@@ -56,5 +60,5 @@
(defn footer-container
[theme]
{:margin-bottom -10
:background-color (colors/theme-colors colors/white colors/neutral-100 theme)})
{:margin-bottom -12
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
@@ -254,6 +254,7 @@
[rn/view {:style style/container}
[floating-button-page/view
{:footer-container-padding 0
:content-container-style style/floating-button-page-content-container
:header [quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back
@@ -1,27 +1,29 @@
(ns status-im.contexts.wallet.swap.setup-swap.view
(:require [clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[status-im.common.controlled-input.utils :as controlled-input]
[status-im.common.events-helper :as events-helper]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
[status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings]
[status-im.contexts.wallet.swap.setup-swap.style :as style]
[status-im.contexts.wallet.swap.utils :as swap-utils]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.number :as number]
[utils.re-frame :as rf]
[utils.string :as utils.string]))
(:require
[clojure.string :as string]
[oops.core :as oops]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[status-im.common.controlled-input.utils :as controlled-input]
[status-im.common.events-helper :as events-helper]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
[status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings]
[status-im.contexts.wallet.swap.setup-swap.style :as style]
[status-im.contexts.wallet.swap.utils :as swap-utils]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.number :as number]
[utils.re-frame :as rf]
[utils.string :as utils.string]))
(def ^:private default-text-for-unfocused-input "0.00")
@@ -85,7 +87,7 @@
[{:keys [input-state on-max-press on-input-focus on-token-press on-approve-press input-focused?]}]
(let [account-color (rf/sub [:wallet/current-viewing-account-color])
network (rf/sub [:wallet/swap-network])
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
pay-token-symbol (:symbol (rf/sub [:wallet/swap-asset-to-pay]))
currency (rf/sub [:profile/currency])
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
swap-proposal (rf/sub [:wallet/swap-proposal-without-fees])
@@ -96,14 +98,19 @@
approval-transaction-id (rf/sub [:wallet/swap-approval-transaction-id])
approved-amount (rf/sub [:wallet/swap-approved-amount])
error-response (rf/sub [:wallet/swap-error-response])
asset-to-pay (rf/sub [:wallet/token-by-symbol pay-token-symbol])
overlay-shown? (boolean (:sheets (rf/sub [:bottom-sheet])))
input-ref (rn/use-ref-atom nil)
set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)))
pay-input-num-value (controlled-input/value-numeric input-state)
pay-input-amount (controlled-input/input-value input-state)
pay-token-symbol (:symbol asset-to-pay)
pay-token-decimals (:decimals asset-to-pay)
pay-token-balance-selected-chain (get-in asset-to-pay
[:balances-per-chain
(:chain-id network) :balance]
0)
pay-token-balance-selected-chain (number/convert-to-whole-number
(get-in asset-to-pay
[:balances-per-chain
(:chain-id network) :raw-balance]
0)
pay-token-decimals)
pay-token-fiat-value (str
(utils/calculate-token-fiat-value
{:currency currency
@@ -111,10 +118,15 @@
:token asset-to-pay}))
available-crypto-limit (money/bignumber
pay-token-balance-selected-chain)
display-decimals (min pay-token-decimals
constants/min-token-decimals-to-display)
available-crypto-limit-display (number/remove-trailing-zeroes
(.toFixed available-crypto-limit
(min pay-token-decimals
constants/min-token-decimals-to-display)))
(.toFixed available-crypto-limit display-decimals))
available-crypto-limit-display (if (and (= available-crypto-limit-display "0")
(money/greater-than available-crypto-limit
(money/bignumber 0)))
(number/small-number-threshold display-decimals)
available-crypto-limit-display)
approval-amount-required-num (when approval-amount-required
(str (number/hex->whole approval-amount-required
pay-token-decimals)))
@@ -122,8 +134,7 @@
(money/greater-than
(money/bignumber pay-input-amount)
available-crypto-limit))
(money/equal-to (money/bignumber
available-crypto-limit-display)
(money/equal-to available-crypto-limit
(money/bignumber 0)))
valid-pay-input? (and
(not (string/blank?
@@ -141,8 +152,15 @@
(fn []
(request-fetch-swap-proposal))
[pay-input-amount])
(rn/use-effect
(fn []
(when-not overlay-shown?
(some-> @input-ref
(oops/ocall "focus"))))
[overlay-shown?])
[quo/swap-input
{:type :pay
{:get-ref set-input-ref
:type :pay
:error? pay-input-error?
:token pay-token-symbol
:customization-color :blue
@@ -156,7 +174,7 @@
:else :disabled)
:currency-symbol currency-symbol
:on-token-press on-token-press
:on-max-press #(on-max-press (str available-crypto-limit))
:on-max-press #(on-max-press (str pay-token-balance-selected-chain))
:on-input-focus on-input-focus
:value pay-input-amount
:fiat-value pay-token-fiat-value
@@ -274,23 +292,28 @@
(defn- pay-token-bottom-sheet
[]
(let [asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])]
(let [asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])
network (rf/sub [:wallet/swap-network])]
[select-asset/view
{:title (i18n/label :t/select-asset-to-pay)
:network network
:on-select (fn [token]
(rf/dispatch [:wallet.swap/select-asset-to-pay {:token token}]))
:hide-token-fn (fn [type {:keys [available-balance]}]
(and (= type constants/swap-tokens-my)
(= available-balance 0)))
:hide-token-fn (fn [type {:keys [balances-per-chain]}]
(let [balance (get-in balances-per-chain [(:chain-id network) :balance] "0")]
(and (= type constants/swap-tokens-my)
(= balance "0"))))
:disable-token-fn (fn [_ token]
(= (:symbol token)
(:symbol asset-to-receive)))}]))
(defn- receive-token-bottom-sheet
[]
(let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])]
(let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
network (rf/sub [:wallet/swap-network])]
[select-asset/view
{:title (i18n/label :t/select-asset-to-receive)
:network network
:on-select (fn [token]
(rf/dispatch [:wallet.swap/select-asset-to-receive {:token token}]))
:disable-token-fn (fn [_ token]
@@ -310,10 +333,12 @@
network (rf/sub [:wallet/swap-network])
pay-input-amount (controlled-input/input-value pay-input-state)
pay-token-decimals (:decimals asset-to-pay)
pay-token-balance-selected-chain (get-in asset-to-pay
[:balances-per-chain
(:chain-id network) :balance]
0)
pay-token-balance-selected-chain (number/convert-to-whole-number
(get-in asset-to-pay
[:balances-per-chain
(:chain-id network) :raw-balance]
0)
pay-token-decimals)
pay-input-error? (and (not (string/blank? pay-input-amount))
(money/greater-than
(money/bignumber pay-input-amount)
@@ -415,13 +440,17 @@
(when (and swap-amount refetch-interval)
(js/clearTimeout @refetch-interval)
(reset! refetch-interval nil))
(if (and swap-amount (not= swap-amount pay-input-amount))
(set-pay-input-state
(fn [input-state]
(controlled-input/set-input-value
input-state
swap-amount)))
(refetch-swap-proposal)))))
(cond (and swap-amount (not= swap-amount pay-input-amount))
(set-pay-input-state
(fn [input-state]
(controlled-input/set-input-value
input-state
swap-amount)))
(and pay-input-amount
(not (number/valid-decimal-count? pay-input-amount (:decimals asset-to-pay))))
(set-pay-input-state controlled-input/delete-all)
:else
(refetch-swap-proposal)))))
[asset-to-pay])
(rn/use-effect
refetch-swap-proposal
@@ -431,7 +460,10 @@
{:on-press on-close
:icon-name :i/arrow-left
:margin-top (safe-area/get-top)
:switcher-type :select-account}]
:switcher-type :select-account
:params {:show-account-balances? true
:asset-symbol (:symbol asset-to-pay)
:network network}}]
[rn/view {:style style/inputs-container}
[pay-token-input
{:input-state pay-input-state
@@ -453,7 +485,7 @@
:on-token-press #(rf/dispatch [:show-bottom-sheet {:content receive-token-bottom-sheet}])
:on-input-focus #(set-pay-input-focused? false)}]]
[rn/view {:style style/footer-container}
[alert-banner {:pay-input-error? pay-input-error?}]
(when-not loading-swap-proposal? [alert-banner {:pay-input-error? pay-input-error?}])
(when (or loading-swap-proposal? swap-proposal)
[transaction-details])
[action-button {:on-press on-review-swap-press}]]
@@ -34,9 +34,8 @@
(defn tokens-by-symbol
[props]
(tokens-by-key (assoc props
:key-fn
(fn [{:keys [address] :as token}]
(if (get-in token [:communityData :id])
address
(:symbol token))))))
(tokens-by-key
(assoc props
:key-fn
(fn [{:keys [chainId address] :as token}]
(str chainId "-" (if (get-in token [:communityData :id]) address (:symbol token)))))))
@@ -75,7 +75,7 @@
:image nil})
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ETH"
:key "1-ETH"
:community-id nil
:symbol "ETH"
:sources ["native"]
@@ -86,7 +86,7 @@
:image nil}
{:address "0x0000000000000000000000000000000000000001"
:decimals 18
:key "SNT"
:key "1-SNT"
:community-id nil
:symbol "SNT"
:sources ["native"]
@@ -174,7 +174,7 @@
:image nil})
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ETH"
:key "1-ETH"
:community-id nil
:symbol "ETH"
:sources ["native"]
@@ -185,7 +185,7 @@
:image nil}
{:address "0x0000000000000000000000000000000000000001"
:decimals 18
:key "SNT"
:key "1-SNT"
:community-id nil
:symbol "SNT"
:sources ["native" "second"]
@@ -196,7 +196,7 @@
:image nil}
{:address "0x0000000000000000000000000000000000000002"
:decimals 18
:key "ANOTHER1"
:key "1-ANOTHER1"
:community-id nil
:symbol "ANOTHER1"
:sources ["second"]
@@ -253,7 +253,7 @@
:image nil})
:by-symbol '({:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ETH"
:key "1-ETH"
:community-id nil
:symbol "ETH"
:sources ["native"]
@@ -264,7 +264,7 @@
:image nil}
{:address "0x0000000000000000000000000000000000000000"
:decimals 18
:key "ANOTHER1"
:key "1-ANOTHER1"
:community-id nil
:symbol "ANOTHER1"
:sources ["native" "second"]
+1
View File
@@ -41,6 +41,7 @@
status-im.contexts.syncing.events
status-im.contexts.wallet.add-account.add-address-to-watch.events
status-im.contexts.wallet.add-account.create-account.events
status-im.contexts.wallet.buy-crypto.events
status-im.contexts.wallet.collectible.events
status-im.contexts.wallet.common.wizard.events
status-im.contexts.wallet.effects
+2 -1
View File
@@ -32,7 +32,8 @@
::wallet.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED)
::wallet.saved-addresses (enabled-in-env? :WALLET_SAVED_ADDRESSES)
::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED)
::wallet.custom-network-amounts (enabled-in-env? :FLAG_WALLET_CUSTOM_NETWORK_AMOUNTS_ENABLED)})
::wallet.custom-network-amounts (enabled-in-env? :FLAG_WALLET_CUSTOM_NETWORK_AMOUNTS_ENABLED)
::keycard.migrate-profile false})
(defonce ^:private feature-flags-config
(reagent/atom initial-flags))
+6
View File
@@ -69,6 +69,12 @@
transitions/new-to-status-modal-animations
transitions/push-animations-for-transparent-background)}))
(def keycard-modal-screen-options
(assoc transparent-modal-screen-options
:layout nil
:theme :dark
:insets {:top? true :bottom? true}))
(def sheet-options
{:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]
+27 -1
View File
@@ -26,6 +26,10 @@
[status-im.contexts.communities.actions.share-community.view :as share-community]
[status-im.contexts.communities.discover.view :as communities.discover]
[status-im.contexts.communities.overview.view :as communities.overview]
[status-im.contexts.keycard.check.view :as keycard.check]
[status-im.contexts.keycard.empty.view :as keycard.empty]
[status-im.contexts.keycard.error.view :as keycard.error]
[status-im.contexts.keycard.not-keycard.view :as keycard.not-keycard]
[status-im.contexts.onboarding.create-or-sync-profile.view :as create-or-sync-profile]
[status-im.contexts.onboarding.create-password.view :as create-password]
[status-im.contexts.onboarding.create-profile.view :as create-profile]
@@ -58,6 +62,7 @@
[status-im.contexts.profile.settings.screens.password.view :as settings-password]
[status-im.contexts.profile.settings.screens.syncing.view :as settings.syncing]
[status-im.contexts.profile.settings.view :as settings]
[status-im.contexts.settings.keycard.view :as settings.keycard]
[status-im.contexts.settings.language-and-currency.currency.view :as settings.currency-selection]
[status-im.contexts.settings.language-and-currency.view :as settings.language-and-currency]
[status-im.contexts.settings.privacy-and-security.share-usage.view :as settings.share-usage]
@@ -584,6 +589,10 @@
:options options/transparent-modal-screen-options
:component wallet-options/view}
{:name :screen/settings.keycard
:options options/keycard-modal-screen-options
:component settings.keycard/view}
{:name :screen/settings.rename-keypair
:options options/transparent-screen-options
:component keypair-rename/view}
@@ -671,7 +680,24 @@
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false})
:component change-password-loading/view}]
:component change-password-loading/view}
;; Keycard
{:name :screen/keycard.check
:options options/keycard-modal-screen-options
:component keycard.check/view}
{:name :screen/keycard.empty
:options options/keycard-modal-screen-options
:component keycard.empty/view}
{:name :screen/keycard.error
:options options/keycard-modal-screen-options
:component keycard.error/view}
{:name :screen/keycard.not-keycard
:options options/keycard-modal-screen-options
:component keycard.not-keycard/view}]
[{:name :shell
:options {:theme :dark}}]
+2 -2
View File
@@ -11,7 +11,7 @@
[status-im.common.bottom-sheet-screen.view :as bottom-sheet-screen]
[status-im.common.bottom-sheet.view :as bottom-sheet]
[status-im.common.toasts.view :as toasts]
[status-im.contexts.keycard.sheet.view :as keycard.sheet]
[status-im.contexts.keycard.nfc-sheet.view :as keycard.sheet]
[status-im.navigation.screens :as screens]
[status-im.setup.hot-reload :as reloader]
[utils.re-frame :as rf]))
@@ -47,7 +47,7 @@
(merge
{:flex 1
:margin-top alert-banners-top-margin
:background-color (or background-color (colors/theme-colors colors/white colors/neutral-100 theme))}
:background-color (or background-color (colors/theme-colors colors/white colors/neutral-95 theme))}
(when bottom?
{:padding-bottom (safe-area/get-bottom)})
(when top?
+5
View File
@@ -36,3 +36,8 @@
(fn [keycard]
(:connection-sheet-opts keycard)))
(rf/reg-sub
:keycard/application-info-error
:<- [:keycard]
(fn [keycard]
(:application-info-error keycard)))
+6
View File
@@ -326,6 +326,12 @@
(fn [profile]
(profile.utils/photo profile)))
(re-frame/reg-sub
:profile/name
:<- [:profile/profile]
(fn [profile]
(profile.utils/displayed-name profile)))
(re-frame/reg-sub
:profile/login-profile
:<- [:profile/login]
+25
View File
@@ -5,3 +5,28 @@
:wallet/crypto-on-ramps
:<- [:wallet]
:-> :crypto-on-ramps)
(rf/reg-sub
:wallet/wallet-buy-crypto
:<- [:wallet/ui]
:-> :buy-crypto)
(rf/reg-sub
:wallet/wallet-buy-crypto-account
:<- [:wallet/wallet-buy-crypto]
:-> :account)
(rf/reg-sub
:wallet/wallet-buy-crypto-provider
:<- [:wallet/wallet-buy-crypto]
:-> :provider)
(rf/reg-sub
:wallet/wallet-buy-crypto-recurrent?
:<- [:wallet/wallet-buy-crypto]
:-> :recurrent?)
(rf/reg-sub
:wallet/wallet-buy-crypto-network
:<- [:wallet/wallet-buy-crypto]
:-> :network)
+28 -7
View File
@@ -399,6 +399,22 @@
(assoc :balance balance
:formatted-balance formatted-balance)))))
(rf/reg-sub
:wallet/current-viewing-account-or-default
:<- [:wallet/accounts]
:<- [:wallet/current-viewing-account-address]
:<- [:wallet/balances-in-selected-networks]
:<- [:profile/currency-symbol]
(fn [[accounts current-viewing-account-address balances currency-symbol]]
(let [account (or (utils/get-account-by-address accounts current-viewing-account-address)
(utils/get-default-account accounts))
address (:address account)
balance (get balances address)
formatted-balance (utils/prettify-balance currency-symbol balance)]
(assoc account
:balance balance
:formatted-balance formatted-balance))))
(rf/reg-sub
:wallet/current-viewing-account-color
:<- [:wallet/current-viewing-account]
@@ -420,7 +436,7 @@
(rf/reg-sub
:wallet/current-viewing-account-tokens-filtered
:<- [:wallet/current-viewing-account]
:<- [:wallet/current-viewing-account-or-default]
:<- [:wallet/network-details]
:<- [:wallet/wallet-send]
(fn [[account networks send-data] [_ {:keys [query chain-ids hide-token-fn]}]]
@@ -454,14 +470,19 @@
(rf/reg-sub
:wallet/tokens-filtered
:<- [:wallet/tokens]
(fn [{:keys [by-symbol market-values-per-token details-per-token]}
:<- [:profile/currency]
(fn [[{:keys [by-symbol market-values-per-token details-per-token prices-per-token]} currency]
[_ {:keys [query chain-ids hide-token-fn]}]]
(let [tokens (->> by-symbol
(map (fn [token]
(-> token
(assoc :market-values
(get market-values-per-token (:symbol token)))
(assoc :details (get details-per-token (:symbol token))))))
(let [token-symbol (keyword (:symbol token))
price (get-in prices-per-token [token-symbol currency])]
(-> token
(assoc :market-values
(get market-values-per-token token-symbol))
(assoc :details (get details-per-token token-symbol))
(assoc-in [:market-values-per-currency currency :price]
price)))))
(filter (fn [{:keys [chain-id]}]
(some #{chain-id} chain-ids)))
(remove #(when hide-token-fn
@@ -476,7 +497,7 @@
(rf/reg-sub
:wallet/token-by-symbol
:<- [:wallet/current-viewing-account]
:<- [:wallet/current-viewing-account-or-default]
:<- [:wallet/network-details]
(fn [[{:keys [tokens]} networks] [_ token-symbol chain-ids]]
(->> (utils/tokens-with-balance tokens networks chain-ids)
+6 -3
View File
@@ -239,9 +239,12 @@
(defn crypto->fiat
[crypto fiat-price]
(when-let [^js bn (bignumber crypto)]
(-> (.times bn ^js (bignumber fiat-price))
(with-precision 2))))
(let [^js crypto-bn (bignumber crypto)
^js fiat-price-bn (bignumber fiat-price)]
(when (and crypto-bn fiat-price-bn)
(-> crypto-bn
(.times fiat-price-bn)
(with-precision 2)))))
(defn sufficient-funds?
[^js amount ^js balance]
+32 -13
View File
@@ -2,7 +2,8 @@
(:require [clojure.string :as string]
[native-module.core :as native-module]
[utils.hex :as utils.hex]
[utils.money :as utils.money]))
[utils.money :as utils.money]
[utils.money :as money]))
(defn naive-round
"Quickly and naively round number `n` up to `decimal-places`.
@@ -18,17 +19,6 @@
(/ (Math/round (* n scale))
scale)))
(defn convert-to-whole-number
"Converts a fractional `amount` to its corresponding whole number representation
by dividing it by 10 raised to the power of `decimals`. This is often used in financial
calculations where amounts are stored in their smallest units (e.g., cents) and need
to be converted to their whole number equivalents (e.g., dollars).
Example usage:
(convert-to-whole-number 12345 2) ; => 123.45"
[amount decimals]
(/ amount (Math/pow 10 decimals)))
(defn parse-int
"Parses `n` as an integer. Defaults to zero or `default` instead of NaN."
([n]
@@ -65,15 +55,44 @@
"")
""))))
(defn convert-to-whole-number
"Converts a fractional `amount` to its corresponding whole number representation
by dividing it by 10 raised to the power of `decimals`. This is often used in financial
calculations where amounts are stored in their smallest units (e.g., cents) and need
to be converted to their whole number equivalents (e.g., dollars).
Example usage:
(convert-to-whole-number 12345 2) ; => 123.45"
[amount decimals]
(-> amount
(/ (Math/pow 10 decimals))
(.toFixed decimals)
remove-trailing-zeroes))
(defn hex->whole
[num decimals]
(-> num
utils.hex/normalize-hex
native-module/hex-to-number
(convert-to-whole-number decimals)))
(convert-to-whole-number decimals)
money/bignumber))
(defn to-fixed
[num decimals]
(-> num
(utils.money/to-fixed decimals)
remove-trailing-zeroes))
(defn small-number-threshold
"Receives a decimal count and returns a string like '<0.001' if the decimal count is 3,
'<0.000001' if the decimal count is 6, etc."
[decimal-count]
(if (> decimal-count 0)
(str "<0." (apply str (repeat (dec decimal-count) "0")) "1")
"0"))
(defn valid-decimal-count?
"Returns false if the number has more decimals than the decimal count, otherwise true."
[num decimal-count]
(let [decimal-part (second (string/split (str num) #"\."))]
(or (nil? decimal-part) (<= (count decimal-part) decimal-count))))
+44 -11
View File
@@ -5,23 +5,23 @@
(deftest convert-to-whole-number-test
(testing "correctly converts fractional amounts to whole numbers"
(is (= 123.45 (utils.number/convert-to-whole-number 12345 2)))
(is (= 1.2345 (utils.number/convert-to-whole-number 12345 4)))
(is (= 12345.0 (utils.number/convert-to-whole-number 1234500 2)))
(is (= 0.123 (utils.number/convert-to-whole-number 123 3)))
(is (= 1000.0 (utils.number/convert-to-whole-number 1000000 3))))
(is (= "123.45" (utils.number/convert-to-whole-number 12345 2)))
(is (= "1.2345" (utils.number/convert-to-whole-number 12345 4)))
(is (= "12345" (utils.number/convert-to-whole-number 1234500 2)))
(is (= "0.123" (utils.number/convert-to-whole-number 123 3)))
(is (= "1000" (utils.number/convert-to-whole-number 1000000 3))))
(testing "handles zero decimals"
(is (= 12345 (utils.number/convert-to-whole-number 12345 0))))
(is (= "12345" (utils.number/convert-to-whole-number 12345 0))))
(testing "handles negative amounts"
(is (= -123.45 (utils.number/convert-to-whole-number -12345 2)))
(is (= -1.2345 (utils.number/convert-to-whole-number -12345 4)))
(is (= -0.123 (utils.number/convert-to-whole-number -123 3))))
(is (= "-123.45" (utils.number/convert-to-whole-number -12345 2)))
(is (= "-1.2345" (utils.number/convert-to-whole-number -12345 4)))
(is (= "-0.123" (utils.number/convert-to-whole-number -123 3))))
(testing "handles zero amount"
(is (= 0 (utils.number/convert-to-whole-number 0 2)))
(is (= 0 (utils.number/convert-to-whole-number 0 0)))))
(is (= "0" (utils.number/convert-to-whole-number 0 2)))
(is (= "0" (utils.number/convert-to-whole-number 0 0)))))
(deftest parse-int-test
(testing "defaults to zero"
@@ -50,3 +50,36 @@
(is (= 6 (utils.number/parse-float "6")))
(is (= 6.99 (utils.number/parse-float "6.99" 0)))
(is (= -6.9 (utils.number/parse-float "-6.9" 0)))))
(deftest small-number-threshold-test
(testing "correctly generates threshold strings based on decimal count"
(is (= "<0.1" (utils.number/small-number-threshold 1)))
(is (= "<0.01" (utils.number/small-number-threshold 2)))
(is (= "<0.001" (utils.number/small-number-threshold 3)))
(is (= "<0.000001" (utils.number/small-number-threshold 6)))
(is (= "<0.0000000001" (utils.number/small-number-threshold 10)))
(is (= "<0.000000000000000001" (utils.number/small-number-threshold 18)))
(is (= "<0.000000000000000000001" (utils.number/small-number-threshold 21))))
(testing "handles edge cases for decimal count"
(is (= "0" (utils.number/small-number-threshold 0)))
(is (= "0" (utils.number/small-number-threshold -1)))))
(deftest valid-decimal-count?-test
(testing "valid decimal count check for numbers with varying decimals"
(is (true? (utils.number/valid-decimal-count? 123 2)))
(is (true? (utils.number/valid-decimal-count? 123 0)))
(is (true? (utils.number/valid-decimal-count? 123.45 2)))
(is (true? (utils.number/valid-decimal-count? 123.4 2)))
(is (true? (utils.number/valid-decimal-count? 123.456 3)))
(is (false? (utils.number/valid-decimal-count? 123.456 2)))
(is (false? (utils.number/valid-decimal-count? 123.456789 4)))
(is (true? (utils.number/valid-decimal-count? 123.0 1)))
(is (true? (utils.number/valid-decimal-count? -123.45 2)))
(is (false? (utils.number/valid-decimal-count? -123.4567 3)))
(is (true? (utils.number/valid-decimal-count? 1234567890.12 2)))
(is (false? (utils.number/valid-decimal-count? 1234567890.12345 3)))))
@@ -6,8 +6,3 @@
[distance-from-list-top chat-list-scroll-y callback]
(.messagesListOnScroll ^js worklets distance-from-list-top chat-list-scroll-y callback))
(defn use-messages-scrolled-to-threshold
[distance-from-list-top threshold]
(.useMessagesScrolledToThreshold ^js worklets
distance-from-list-top
threshold))
@@ -1,27 +0,0 @@
(ns utils.worklets.chat.messenger.navigation)
(def ^:private worklets (js/require "../src/js/worklets/chat/messenger/navigation.js"))
(defn navigation-header-opacity
[distance-from-list-top all-loaded? chat-screen-layout-calculations-complete? start-position]
(.navigationHeaderOpacity ^js worklets
distance-from-list-top
all-loaded?
chat-screen-layout-calculations-complete?
start-position))
(defn navigation-header-position
[distance-from-list-top all-loaded? top-bar-height start-position]
(.navigationHeaderPosition ^js worklets
distance-from-list-top
all-loaded?
top-bar-height
start-position))
(defn navigation-buttons-complete-opacity
[chat-screen-layout-calculations-complete?]
(.navigationButtonsCompleteOpacity ^js worklets chat-screen-layout-calculations-complete?))
(defn interpolate-navigation-view-opacity
[props]
(.interpolateNavigationViewOpacity ^js worklets (clj->js props)))
@@ -1,11 +0,0 @@
(ns utils.worklets.chat.messenger.placeholder)
(def ^:private worklets (js/require "../src/js/worklets/chat/messenger/placeholder.js"))
(defn placeholder-opacity
[chat-screen-layout-calculations-complete?]
(.placeholderOpacity ^js worklets chat-screen-layout-calculations-complete?))
(defn placeholder-z-index
[chat-screen-layout-calculations-complete?]
(.placeholderZIndex ^js worklets chat-screen-layout-calculations-complete?))
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v2.3.0",
"commit-sha1": "effde33d493ff52b66c8013067c9aa6444d84473",
"src-sha256": "1rd0mczdkvjm9pbmza234v1jy1bvqh3ni2xck1l42ha4sld2aqjl"
"version": "router-improvements",
"commit-sha1": "a5e7465e27c7043b4e3e755d786355b2c47557d8",
"src-sha256": "0msir6b4gr9kf15krxn9ys3ssx3h2mza4ccw5a62rmlqd4rdh2ya"
}
+1
View File
@@ -125,6 +125,7 @@ class TestrailReport(BaseTestReport):
test_cases['nightly']['activity_centre_contact_request'] = 50984
test_cases['nightly']['activity_centre_other'] = 51005
test_cases['nightly']['wallet'] = 59443
test_cases['nightly']['fallback'] = 63472
## Upgrade e2e
# test_cases['upgrade']['general'] = 881
@@ -374,6 +374,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
def test_community_message_edit(self):
message_before_edit, message_after_edit = 'Message BEFORE edit', "Message AFTER edit 2"
self.channel_1.send_message(message_before_edit)
self.channel_1.chat_element_by_text(message_before_edit).wait_for_sent_state()
self.channel_1.edit_message_in_chat(message_before_edit, message_after_edit)
for channel in (self.channel_1, self.channel_2):
if not channel.element_by_text_part(message_after_edit).is_element_displayed(60):
@@ -391,6 +392,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
message_to_delete_everyone = 'delete for everyone'
message_to_delete_for_me = 'delete for me'
self.channel_2.send_message(message_to_delete_everyone)
self.channel_2.chat_element_by_text(message_to_delete_everyone).wait_for_sent_state()
self.home_2.just_fyi('Delete for message everyone. Checking that message is deleted for all members')
self.channel_2.delete_message_in_chat(message_to_delete_everyone)
for channel in (self.channel_1, self.channel_2):
+199
View File
@@ -0,0 +1,199 @@
import pytest
from selenium.common import TimeoutException
from base_test_case import MultipleSharedDeviceTestCase, create_shared_drivers
from tests import marks, run_in_parallel, transl
from users import transaction_senders
from views.sign_in_view import SignInView
@pytest.mark.xdist_group(name="new_six_2")
@marks.nightly
class TestFallbackMultipleDevice(MultipleSharedDeviceTestCase):
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(3)
self.sign_in_1, self.sign_in_2, self.sign_in_3 = SignInView(self.drivers[0]), SignInView(
self.drivers[1]), SignInView(self.drivers[2])
self.home_1, self.home_2, self.home_3 = self.sign_in_1.get_home_view(), self.sign_in_2.get_home_view(), \
self.sign_in_3.get_home_view()
self.sign_in_1.just_fyi("Device 1: create a new user")
self.sign_in_3.just_fyi("Device 3: create a new user")
self.user_name_1, self.user_name_3 = 'first user', 'third user'
self.loop.run_until_complete(
run_in_parallel(((self.sign_in_1.create_user, {'username': self.user_name_1}),
(self.sign_in_3.create_user, {'username': self.user_name_3}))))
self.profile_1 = self.home_1.profile_button.click()
self.profile_2 = self.home_2.get_profile_view()
self.sign_in_3.just_fyi("Device 3: get public key")
self.recovery_phrase, self.public_key_3 = self.loop.run_until_complete(
run_in_parallel(((self.profile_1.backup_recovery_phrase, {}),
(self.home_3.get_public_key, {}))))
self.home_2.driver.get_clipboard_text() # just pinging 2nd device to save the connection
self.profile_1.click_system_back_button()
self.home_1.chats_tab.click()
self.home_1.just_fyi("Device 1: add the 3rd user as a contact")
self.home_1.add_contact(self.public_key_3)
self.home_3.just_fyi("Device 3: accepting contact request from the 1st user")
self.home_3.handle_contact_request(self.user_name_1)
self.profile_1.just_fyi("Device 1: get sync code")
self.home_1.profile_button.click()
self.sync_code = self.profile_1.get_sync_code()
@marks.testrail_id(740220)
def test_fallback_sync_with_error(self):
self.sign_in_2.just_fyi("Device 2: try syncing profile")
self.sign_in_2.sync_profile(sync_code=self.sync_code)
self.sign_in_2.progress_screen_title.wait_for_element()
assert self.sign_in_2.progress_screen_title.text == "Oops, somethings wrong"
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
@marks.testrail_id(740221)
def test_fallback_with_correct_seed_phrase(self):
self.sign_in_2.just_fyi("Device 2: recover a profile with backed up seed phrase")
self.sign_in_2.try_seed_phrase_button.click()
self.sign_in_2.recover_access(passphrase=self.recovery_phrase, after_sync_code=True)
self.profile_1.click_system_back_button()
self.home_2.just_fyi("Getting device 2 name")
self.home_2.profile_button.click()
self.profile_2.syncing_button.scroll_and_click()
self.profile_2.paired_devices_button.click()
device_2_name = self.profile_2.get_current_device_name()
self.profile_2.click_system_back_button(times=3)
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
self.profile_1.just_fyi("Check device 2 is shown in not paired devices list in profile 1")
device_1_name = self.profile_1.get_current_device_name()
device_element = self.profile_1.get_paired_device_by_name(device_2_name)
if device_element.is_element_displayed():
if not device_element.get_pair_button.is_element_displayed():
self.errors.append(
"Pair button is absent for the device 2 inside Paired devices list of profile 1 before pairing")
else:
self.errors.append("Device 2 is not shown in Paired devices list for device 1 before pairing")
self.profile_1.click_system_back_button(times=3)
for home in self.home_1, self.home_2:
home.notifications_unread_badge.wait_for_visibility_of_element(30)
home.open_activity_center_button.click_until_presence_of_element(home.close_activity_centre)
self.home_1.just_fyi("Checking pairing request on device 1")
a_c_element = self.home_1.get_activity_center_element_by_text(transl['review-pairing-request'])
if a_c_element.title.text != transl['new-device-detected']:
self.errors.append(
"Notification with title '%s' is not shown in the activity center for the device 1" % transl[
'new-device-detected'])
a_c_element.review_pairing_request_button.click()
device_id_1 = self.home_1.get_new_device_installation_id()
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
self.home_2.just_fyi("Checking sync profile on device 2")
a_c_element = self.home_2.get_activity_center_element_by_text(transl['more-details'])
if a_c_element.title.text != transl['sync-your-profile']:
self.errors.append(
"Notification with title '%s' is not shown in the activity center for the device 2" % transl[
'sync-your-profile'])
a_c_element.more_details_button.click()
device_id_2 = self.home_2.get_new_device_installation_id()
if device_id_1 != device_id_2:
self.errors.append("Device ids don't match on the activity center notifications")
self.home_1.just_fyi("Confirm pairing request on device 1")
self.home_1.element_by_translation_id('pair-and-sync').click()
self.home_2.element_by_translation_id('close').click()
self.home_1.close_activity_centre.click()
self.home_2.close_activity_centre.click()
self.home_3.chats_tab.is_element_displayed() # just pinging 3rd device to save the connection
self.home_1.just_fyi("Device 1: Check that the device 2 is shown in paired devices list")
self.home_1.profile_button.click()
self.profile_1.syncing_button.scroll_and_click()
self.profile_1.paired_devices_button.click()
device_element = self.profile_1.get_paired_device_by_name(device_2_name)
if device_element.is_element_displayed():
if not device_element.get_unpair_button.is_element_displayed():
self.errors.append(
"Unpair button is absent for the device 2 inside Paired devices list of profile 1 after pairing")
else:
self.errors.append("Device 2 is not shown in Paired devices list for device 1 after pairing")
self.home_2.just_fyi("Device 2: Check that the device 1 is shown paired devices list")
self.home_2.profile_button.click()
self.profile_2.syncing_button.scroll_and_click()
self.profile_2.paired_devices_button.click()
device_element = self.profile_2.get_paired_device_by_name(device_1_name)
if device_element.is_element_displayed():
if not device_element.get_unpair_button.is_element_displayed():
self.errors.append(
"Unpair button is absent for the device 1 inside Paired devices list of profile 2 after pairing")
else:
self.errors.append("Device 1 is not shown in Paired devices list for device 2 after pairing")
self.home_3.just_fyi("Device 3: send a message to user 1")
self.home_3.chats_tab.click()
chat_3 = self.home_3.get_chat(self.user_name_1).click()
message = "Test message"
chat_3.send_message(message)
def _check_message(home_view, index):
home_view.just_fyi("Device %s: check the message from the user 3 is received" % index)
home_view.click_system_back_button(times=3)
home_view.chats_tab.click()
try:
chat_element = home_view.get_chat(self.user_name_3)
chat_element.wait_for_visibility_of_element(60)
chat_view = chat_element.click()
chat_view.chat_element_by_text(message).wait_for_visibility_of_element(60)
except TimeoutException:
self.errors.append("Message is not received by the user %s" % index)
self.loop.run_until_complete(
run_in_parallel(((_check_message, {'home_view': self.home_1, 'index': 1}),
(_check_message, {'home_view': self.home_2, 'index': 2}))))
self.errors.verify_no_errors()
@marks.testrail_id(740222)
def test_fallback_validate_seed_phrase(self):
self.sign_in_2.reopen_app(sign_in=False)
self.sign_in_2.just_fyi("Device 2: try syncing profile")
self.sign_in_2.sync_profile(sync_code=self.sync_code, first_user=False)
self.sign_in_2.progress_screen_title.wait_for_element()
assert self.sign_in_2.progress_screen_title.text == "Oops, somethings wrong"
self.sign_in_2.just_fyi("Device 2: try invalid passphrase")
self.sign_in_2.try_seed_phrase_button.click()
self.sign_in_2.passphrase_edit_box.send_keys(' '.join(['asset'] * 12))
self.sign_in_2.continue_button.click()
if not self.sign_in_2.element_by_translation_id('seed-phrase-invalid').is_element_displayed():
self.errors.append("Error message is not displayed for invalid recovery phrase")
self.sign_in_2.just_fyi("Device 2: try creating an account with another valid passphrase")
self.sign_in_2.passphrase_edit_box.clear()
self.sign_in_2.passphrase_edit_box.send_keys(transaction_senders['A']['passphrase'])
self.sign_in_2.continue_button.click()
if not self.sign_in_2.profile_title_input.is_element_displayed():
self.errors.append("Can't recover an access with a valid passphrase")
self.sign_in_2.click_system_back_button()
self.sign_in_2.just_fyi("Device 2: try recovering an account which is already synced")
self.sign_in_2.passphrase_edit_box.clear()
self.sign_in_2.passphrase_edit_box.send_keys(self.recovery_phrase)
self.sign_in_2.continue_button.click()
try:
self.sign_in_2.native_alert_title.wait_for_element()
shown_text = self.sign_in_2.native_alert_title.text
if shown_text != "Keys for this account already exist":
self.errors.append("Incorrect error message '%s' is shown for already synced account" % shown_text)
self.sign_in_2.cancel_button.click()
except TimeoutException:
self.errors.append("Error is not shown for already synced account")
self.errors.verify_no_errors()
+8 -7
View File
@@ -298,14 +298,13 @@ class BaseView(object):
self.deny_button = Button(self.driver, translation_id="deny", uppercase=True)
self.continue_button = Button(self.driver, translation_id="continue", uppercase=True)
self.ok_button = Button(self.driver, xpath="//*[@text='OK' or @text='Ok']")
self.next_button = Button(self.driver, translation_id="next")
self.add_button = Button(self.driver, translation_id="add")
self.save_button = Button(self.driver, translation_id="save")
self.done_button = Button(self.driver, translation_id="done")
self.done_button = Button(self.driver, accessibility_id="Done")
self.delete_button = Button(self.driver, translation_id="delete", uppercase=True)
self.ok_continue_button = Button(self.driver, xpath="//*[@text='OK, CONTINUE' or @text='Okay, continue']")
self.ok_continue_button = Button(self.driver, accessibility_id="Okay, continue")
self.discard_button = Button(self.driver, xpath="//*[@text='DISCARD']")
self.confirm_button = Button(self.driver, translation_id='confirm', uppercase=True)
self.confirm_button = Button(self.driver, accessibility_id='Confirm')
self.cross_icon = Button(self.driver, xpath="(//android.widget.ImageView[@content-desc='icon'])[1]")
self.close_sticker_view_icon = Button(self.driver, xpath="//androidx.appcompat.widget.LinearLayoutCompat")
@@ -314,7 +313,7 @@ class BaseView(object):
self.navigate_up_button = Button(self.driver, accessibility_id="Navigate Up")
self.show_roots_button = Button(self.driver, accessibility_id="Show roots")
self.get_started_button = Button(self.driver, translation_id="get-started")
self.ok_got_it_button = Button(self.driver, translation_id="ok-got-it")
self.ok_got_it_button = Button(self.driver, accessibility_id="Okay, got it")
self.cross_icon_inside_welcome_screen_button = Button(self.driver, accessibility_id='hide-home-button')
self.status_in_background_button = Button(self.driver, xpath="//*[contains(@content-desc,'Status')]")
self.cancel_button = Button(self.driver, translation_id="cancel", uppercase=True)
@@ -324,6 +323,7 @@ class BaseView(object):
self.sign_in_phrase = SignInPhraseText(self.driver)
self.toast_content_element = BaseElement(self.driver, accessibility_id="toast-content")
self.next_button = Button(self.driver, accessibility_id="next-button")
self.native_alert_title = Text(self.driver, xpath="//*[@resource-id='android:id/alertTitle']")
# share contact screen
self.show_qr_code_button = Button(self.driver, accessibility_id="show-qr-button")
@@ -332,7 +332,8 @@ class BaseView(object):
self.sharing_text_native = Text(self.driver, xpath="//*[@resource-id='android:id/content_preview_text']")
# checkboxes and toggles
self.checkbox_button = CheckBox(self.driver, accessibility_id="checkbox-off")
self.checkbox_button = CheckBox(
self.driver, xpath="//*[@content-desc='checkbox-off'][@resource-id='checkbox-component']")
self.slide_button_track = SlideButton(self.driver)
# external browser
@@ -770,7 +771,7 @@ class BaseView(object):
self.close_native_device_dialog("MmsService")
def set_device_to_offline(self):
# setting network connection to data only and switching off wifi
# setting network connection to data only and switching off Wi-Fi
self.driver.set_network_connection(2)
self.driver.toggle_wifi()
+2 -4
View File
@@ -1016,9 +1016,8 @@ class ChatView(BaseView):
def edit_message_in_chat(self, message_to_edit, message_to_update):
self.driver.info("Looking for message '%s' to edit it" % message_to_edit)
element = self.element_by_translation_id("edit-message")
self.chat_element_by_text(message_to_edit).message_body.long_press_until_element_is_shown(element)
element.click()
self.chat_element_by_text(message_to_edit).message_body.long_press_element()
self.element_by_translation_id("edit-message").click()
self.chat_message_input.clear()
self.chat_message_input.send_keys(message_to_update)
self.send_message_button.click()
@@ -1029,7 +1028,6 @@ class ChatView(BaseView):
delete_button = self.element_by_translation_id("delete-for-everyone")
else:
delete_button = self.element_by_translation_id("delete-for-me")
# self.chat_element_by_text(message).message_body.long_press_until_element_is_shown(delete_button)
self.chat_element_by_text(message).message_body.long_press_element()
delete_button.click()
+14 -1
View File
@@ -152,6 +152,14 @@ class ActivityCenterElement(SilentButton):
def pending_status_tag(self):
return Text(self.driver, xpath=self.locator + '//*[@content-desc="status-tag-pending"]')
@property
def review_pairing_request_button(self):
return Button(self.driver, accessibility_id='Review pairing request')
@property
def more_details_button(self):
return Button(self.driver, accessibility_id='More details')
def handle_cr(self, element_accessibility: str):
Button(
self.driver,
@@ -399,7 +407,7 @@ class HomeView(BaseView):
if self.toast_content_element.is_element_displayed(10):
self.toast_content_element.wait_for_invisibility_of_element()
try:
self.notifications_unread_badge.wait_for_visibility_of_element(30)
self.notifications_unread_badge.wait_for_visibility_of_element(60)
except TimeoutException:
pass
self.open_activity_center_button.click_until_presence_of_element(self.close_activity_centre)
@@ -611,3 +619,8 @@ class HomeView(BaseView):
xpath="//*[@content-desc='%s'][descendant::*[@content-desc='chat-name-text'][@text='%s']]" % (
self.community_card_item.accessibility_id, community_name)
)
def get_new_device_installation_id(self):
element = Text(self.driver, accessibility_id='new-device-installation-id')
element.wait_for_visibility_of_element()
return element.text
+35 -24
View File
@@ -4,7 +4,7 @@ from selenium.common import NoSuchElementException
from tests import common_password
from tests.base_test_case import AbstractTestCase
from views.base_element import Text, Button, EditBox, SilentButton
from views.base_element import Text, Button, EditBox, SilentButton, BaseElement
from views.base_view import BaseView
@@ -68,15 +68,6 @@ class AdvancedButton(Button):
return self.navigate()
class BackupRecoveryPhraseButton(Button):
def __init__(self, driver):
super().__init__(driver, accessibility_id="back-up-recovery-phrase-button")
def click(self):
self.scroll_to_element().click()
return self.navigate()
class RecoveryPhraseTable(Text):
def __init__(self, driver):
super().__init__(driver, translation_id="your-recovery-phrase",
@@ -93,11 +84,6 @@ class RecoveryPhraseWordNumberText(Text):
return int(self.find_element().text.split('#')[1])
class RecoveryPhraseWordInput(EditBox):
def __init__(self, driver):
super(RecoveryPhraseWordInput, self).__init__(driver, xpath="//android.widget.EditText")
class HelpButton(Button):
def __init__(self, driver):
super().__init__(driver, accessibility_id="help-button")
@@ -235,10 +221,13 @@ class ProfileView(BaseView):
self.show_profile_pictures_of = Button(self.driver, accessibility_id="show-profile-pictures")
self.show_profile_pictures_to = Button(self.driver, accessibility_id="show-profile-pictures-to")
## Backup recovery phrase
self.backup_recovery_phrase_button = BackupRecoveryPhraseButton(self.driver)
self.backup_recovery_phrase_button = Button(
self.driver, accessibility_id="icon, Backup recovery phrase, label-component, icon")
self.recovery_phrase_table = RecoveryPhraseTable(self.driver)
self.recovery_phrase_word_number = RecoveryPhraseWordNumberText(self.driver)
self.recovery_phrase_word_input = RecoveryPhraseWordInput(self.driver)
self.recovery_phrase_next_button = Button(self.driver, accessibility_id="Next, icon")
self.recovery_phrase_word_input = EditBox(self.driver, xpath="//android.widget.EditText")
## Dapps permissions
self.dapp_permissions_button = DappPermissionsButton(self.driver)
self.revoke_access_button = Button(self.driver, translation_id="revoke-access")
@@ -290,8 +279,8 @@ class ProfileView(BaseView):
self.advertise_device_button = Button(self.driver, accessibility_id="advertise-device")
self.sync_all_button = Button(self.driver, translation_id="sync-all-devices")
self.syncing_button = Button(self.driver, accessibility_id="icon, Syncing, label-component, icon")
self.paired_devices_button = Button(self.driver,
accessibility_id="icon, Paired devices, 0 devices, label-component, icon")
self.paired_devices_button = Button(
self.driver, xpath="//android.view.ViewGroup[contains(@content-desc,'icon, Paired devices,')]")
self.sync_plus_button = Button(
self.driver,
xpath="//*[@text='Paired devices']/following-sibling::android.view.ViewGroup[@content-desc='icon']")
@@ -425,20 +414,20 @@ class ProfileView(BaseView):
return dict(zip(map(int, text[::2]), text[1::2]))
def backup_recovery_phrase(self):
self.driver.info("## Back up seed phrase", device=False)
self.just_fyi("Back up recovery phrase")
self.backup_recovery_phrase_button.click()
self.ok_continue_button.click()
recovery_phrase = self.get_recovery_phrase()
self.next_button.click()
self.recovery_phrase_next_button.click()
word_number = self.recovery_phrase_word_number.number
self.recovery_phrase_word_input.send_keys(recovery_phrase[word_number])
self.next_button.click()
self.recovery_phrase_next_button.click()
word_number_1 = self.recovery_phrase_word_number.number
self.recovery_phrase_word_input.send_keys(recovery_phrase[word_number_1])
self.done_button.click()
self.yes_button.click()
self.ok_got_it_button.click()
self.driver.info("## Seed phrase is backed up!", device=False)
return recovery_phrase
return ' '.join(recovery_phrase.values())
def edit_profile_picture(self, image_index: int, update_by="Gallery"):
self.driver.info("## Setting custom profile image", device=False)
@@ -555,3 +544,25 @@ class ProfileView(BaseView):
self.login_button.click()
self.wait_for_staleness_of_element(password_input)
return self.password_input.text
def get_current_device_name(self):
element = BaseElement(
self.driver,
xpath="//android.view.ViewGroup/android.view.ViewGroup[@content-desc='status-tag-positive']/..")
return element.attribute_value('content-desc').split(',')[1].strip()
def get_paired_device_by_name(self, device_name: str):
class PairedDeviceElement(BaseElement):
def __init__(self, driver, device_name):
super().__init__(driver, xpath="//*[@content-desc='icon, %s, label-component']" % device_name)
@property
def get_pair_button(self):
return Button(self.driver, xpath=self.locator + "//*[@content-desc='Pair']")
@property
def get_unpair_button(self):
return Button(self.driver, xpath=self.locator + "//*[@content-desc='Unpair']")
return PairedDeviceElement(self.driver, device_name)
+37 -11
View File
@@ -154,6 +154,11 @@ class SignInView(BaseView):
self.create_profile_button = Button(self.driver, accessibility_id='new-to-status-button')
self.not_now_button = Button(self.driver, xpath="//*[@text='Not now']")
self.sync_or_recover_profile_button = Button(self.driver, accessibility_id='already-use-status-button')
self.scan_sync_code_button = Button(self.driver, accessibility_id="scan-sync-code-option-card")
self.enter_sync_code_button = Button(self.driver, accessibility_id="Enter sync code")
self.enter_sync_code_input = EditBox(self.driver, accessibility_id="enter-sync-code-input")
self.progress_screen_title = Text(self.driver, accessibility_id="progress-screen-title")
self.try_seed_phrase_button = Button(self.driver, accessibility_id="try-seed-phrase-button")
self.migration_password_input = EditBox(self.driver, accessibility_id="enter-password-input")
self.access_key_button = AccessKeyButton(self.driver)
@@ -215,6 +220,7 @@ class SignInView(BaseView):
self.show_profiles_button = Button(self.driver, accessibility_id="show-profiles")
self.plus_profiles_button = Button(self.driver, accessibility_id="show-new-account-options")
self.create_new_profile_button = Button(self.driver, accessibility_id="create-new-profile")
self.sync_or_recover_new_profile_button = Button(self.driver, accessibility_id="multi-profile")
self.remove_profile_button = Button(self.driver, accessibility_id="remove-profile")
def set_password(self, password: str):
@@ -282,21 +288,24 @@ class SignInView(BaseView):
return self.get_home_view()
def recover_access(self, passphrase: str, password: str = common_password, keycard=False,
enable_notifications=False, second_user=False, username='Restore user', set_image=False):
enable_notifications=False, second_user=False, username='Restore user', set_image=False,
after_sync_code=False):
self.driver.info("## Recover access(password:%s, keycard:%s)" % (password, str(keycard)), device=False)
if not second_user:
self.terms_and_privacy_checkbox.click()
self.sync_or_recover_profile_button.click_until_presence_of_element(self.generate_keys_button)
self.not_now_button.wait_and_click()
else:
self.show_profiles_button.wait_and_click(20)
self.plus_profiles_button.click()
self.create_new_profile_button.click()
self.use_recovery_phrase_button.click()
if not after_sync_code:
if not second_user:
self.terms_and_privacy_checkbox.click()
self.sync_or_recover_profile_button.click_until_presence_of_element(self.generate_keys_button)
self.not_now_button.wait_and_click()
else:
self.show_profiles_button.wait_and_click(20)
self.plus_profiles_button.click()
self.create_new_profile_button.click()
self.use_recovery_phrase_button.click()
self.passphrase_edit_box.send_keys(passphrase)
self.continue_button.click_until_presence_of_element(self.profile_title_input)
self.set_profile(username, set_image)
if not after_sync_code:
self.set_profile(username, set_image)
self.set_password(password)
if enable_notifications:
self.enable_notifications_button.wait_and_click()
@@ -318,6 +327,23 @@ class SignInView(BaseView):
self.driver.info("## Multiaccount is recovered successfully!", device=False)
return self.get_home_view()
def sync_profile(self, sync_code: str, first_user: bool = True):
if first_user:
self.terms_and_privacy_checkbox.click()
self.sync_or_recover_profile_button.click()
self.not_now_button.click()
else:
self.show_profiles_button.click()
self.plus_profiles_button.click()
self.sync_or_recover_new_profile_button.click()
self.scan_sync_code_button.click()
for checkbox in self.checkbox_button.find_elements():
checkbox.click()
self.continue_button.click()
self.enter_sync_code_button.click()
self.enter_sync_code_input.send_keys(sync_code)
self.confirm_button.click()
def sign_in(self, password=common_password):
self.driver.info("## Sign in (password: %s)" % password, device=False)
self.password_input.wait_for_visibility_of_element(10)
+30 -2
View File
@@ -274,6 +274,7 @@
"buy-crypto-title": "Looks like your wallet is empty",
"buy-eth": "Buy ETH",
"buy-ethereum": "Buy Ethereum",
"buy-keycard": "Buy Keycard",
"by-continuing-you-accept": "By continuing you accept our ",
"camera-access-error": "To grant the required camera permission, please go to your system settings and make sure that Status > Camera is selected.",
"camera-permission-denied": "Permission denied",
@@ -362,6 +363,7 @@
"check-before-syncing-doc-checkbox-2": "Make sure you are logged in on the other device",
"check-before-syncing-doc-checkbox-3": "Disable the firewall and VPN on your devices",
"check-before-syncing-doc-description": "To sync your devices successfully, make sure to check and complete these steps:",
"check-keycard": "Check your Keycard",
"check-on-block-explorer": "Check on block explorer",
"check-on-opensea": "Check on opensea",
"check-other-device-for-pairing": "Check your other device for a pairing request.",
@@ -470,6 +472,7 @@
"connect-with-users": "Connect with users",
"connected": "Connected",
"connected-dapps": "Connected dApps",
"connected-dont-move": "Connected. Dont move your card.",
"connected-peers": "Connected and discovered peers",
"connected-to": "Connected to",
"connecting": "Connecting...",
@@ -827,6 +830,7 @@
"emojihash-description": "A visual representation of your chat key. It will help other users recognize your profile.",
"emojis": "Emojis",
"empty-activity-center": "Your chat notifications\nwill appear here",
"empty-card-info": "To generate new key pair, or import existent non-profile keys to the Keycard, please use the Status desktop app. If you'd like these features on mobile, feel free to upvote them and discuss them in Status community.",
"empty-chat-description": "There are no messages \nin this chat yet",
"empty-chat-description-community": "It's been quiet here for the last {{quiet-hours}}.",
"empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ",
@@ -1073,6 +1077,7 @@
"generating-mnemonic": "Generating seed phrase",
"generic-error": "Error: {{generic-error}}",
"get-a-keycard": "Get a Keycard",
"get-keycard": "Get Keycard",
"get-started": "Get started",
"get-status-at": "Get Status at http://status.im",
"get-stickers": "Get Stickers",
@@ -1132,6 +1137,7 @@
"history-nodes": "Status nodes",
"hit-photos-limit": "You can only add {{max-photos}} photos to your message",
"hold-card": "Hold card to the back\n of your phone",
"hold-phone-near-keycard": "Hold your phone near a Status Keycard",
"hold-to-post-1": "Hold",
"hold-to-post-2": "to post",
"home": "Home",
@@ -1167,10 +1173,12 @@
"import-community-title": "Import a community",
"import-from-keycard": "Import from Keycard",
"import-key-pair": "Import key pair",
"import-key-pair-keycard": "Import profile key pair to Keycard",
"import-keypair-steps": "{{account-name}} was derived from your {{keypair-name}} key pair, which has not yet been imported to this device. To transact using this account, you will need to import the {{keypair-name}} key pair first.",
"import-keypair-to-use-account": "Import key pair to use this account",
"import-private-key": "Import private key",
"import-private-key-info": "New addresses cannot be derived from an account imported from a private key. Import using a seed phrase if you wish to derive addresses.",
"import-profile-key-pair": "Import profile key pair",
"import-to-use-derived-accounts": "Import to use derived accounts",
"import-using-phrase": "Import using recovery phrase",
"in": "in",
@@ -1295,6 +1303,7 @@
"jump-to": "Jump to",
"jun": "Jun",
"K": "K",
"keep-card-steady": "Keep the card steady under the phone",
"keep-key": "KeepKey",
"key": "Key",
"key-managment": "Key management",
@@ -1324,6 +1333,7 @@
"keycard-connected-title": "Connected",
"keycard-desc": "Own a Keycard? Store your keys on it; youll need it for transactions",
"keycard-dont-ask-card": "Don't ask for card to sign in",
"keycard-empty": "Keycard is empty",
"keycard-enter-new-passcode": "Enter new passcode {{step}}/2",
"keycard-error-description": "Connect the card again to continue",
"keycard-error-title": "Connection lost",
@@ -1478,6 +1488,7 @@
"make-admin": "Make admin",
"make-moderator": "Make moderator",
"make-one-it-is-easy-we-promise": "Make one, its easy, we promise!",
"make-sure-keycard": "Make sure the card you scanned is a Keycard.",
"make-sure-no-camera-warning": "Make sure no camera or person can see this screen before revealing",
"manage-connections": "Manage connections from within Application Connections",
"manage-keys-and-storage": "Manage keys and storage",
@@ -1557,6 +1568,9 @@
"messages-from-contacts-only-subtitle": "Only people you added as contacts can start a new chat with you or invite you to a group",
"messages-gap-warning": "Some messages might be missing",
"might-break": "Might break some ÐApps",
"migrate-key-pair-keycard": "Migrate profile key pair to Keycard",
"migrate-key-pair-keycard-default-key": "{{name}} is your default Status key pair. Migrating this key pair to Keycard will require you to use your Keycard to login to Status and to transact with the key pairs derived accounts on all synced devices.\n\nKey pair will be removed from device and stored on Keycard.",
"migrate-key-pair-keycard-info": "Re-encrypting your data with your new Keycard login method may take some time, during which you wont be able to use the app.",
"migration-successful": "Migration successful",
"migration-successful-text": "Account succesfully migrated to Keycard",
"migrations-failed-content": "{{message}}\nschema version: initial {{initial-version}}, current {{current-version}}, last {{last-version}}\n\nA database error occured. Your funds and chat key are safe. Other data, like your chats and contacts, cannot be restored. \"{{erase-multiaccounts-data-button-text}}\" button, will remove all other data and allows you to access your funds and send messages.",
@@ -1780,6 +1794,7 @@
"on-keycard": "On Keycard",
"on-status-tree": "On Status tree",
"on-the-web": "On the web",
"on-uppercase": "On",
"once-enabled-share-metadata": "Once enabled, links posted in the chat may share your metadata with the site",
"one-day": "One day",
"one-month": "One month",
@@ -1794,6 +1809,7 @@
"online-community-member": "Online",
"online-now": "Online now",
"only-mentions": "Only @mentions",
"oops-not-keycard": "Oops, this isnt a Keycard",
"oops-this-qr-does-not-contain-an-address": "Oops! This QR does not contain an address",
"oops-wrong-password": "Oops, wrong password!",
"oops-wrong-word": "Oops! Wrong word",
@@ -1824,6 +1840,7 @@
"outgoing": "Outgoing",
"outgoing-transaction": "Outgoing transaction",
"overview": "Overview",
"own-keycard": "Already own a Keycard?",
"own-your-crypto": "Own your crypto",
"owner": "Owner",
"page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings",
@@ -2004,6 +2021,8 @@
"re-encrypt-key": "Re-encrypt your keys",
"read": "Read",
"read-more": "Read more",
"ready-keycard": "Get your Keycard ready",
"ready-to-scan": "Ready to Scan",
"rearrange-categories": "Rearrange Categories",
"receive": "Receive",
"receive-at-least": "Receive at least",
@@ -2058,6 +2077,7 @@
"remove-network": "Remove network",
"remove-nickname": "Remove nickname",
"remove-nickname-toast": "You have removed {{secondary-name}}'s nickname",
"remove-phone-case": "Remove your phone case if you have one",
"remove-private-key-address-desc": "The account will be removed from all of your synced devices. Make sure you have a backup of your key pair or recovery phrase.",
"remove-profile-confirm-message": "All profile data will removed from device.",
"remove-profile-message": "Remove profile from this device",
@@ -2166,9 +2186,11 @@
"searching-for-activity": "Searching for activity...",
"secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.",
"secret-keys-confirmation-title": "Written the codes down?",
"secure-wallet-card": "A secure and private cold wallet in a card format",
"security": "Security",
"see-details": "See details",
"see-it-again": "SEE IT AGAIN",
"see-keycard-ready": "Lets see whats on your Keycard. Ready?",
"see-recovery-phrase-again": "See recovery phrase again",
"see-sticker-set": "See the full sticker set",
"see-suggestions": "See suggestions",
@@ -2191,11 +2213,13 @@
"select-another-account": "Select another account",
"select-asset": "Select asset",
"select-asset-on": "On",
"select-asset-to-buy": "Select asset to buy",
"select-asset-to-pay": "Select asset to pay",
"select-asset-to-receive": "Select asset to receive",
"select-asset-via": "via",
"select-chat": "Select chat to start messaging",
"select-network": "Select network",
"select-network-for-buying": "Select network for buying",
"select-networks": "Select networks",
"select-new-location-for-keys": "Select a new location to save your private key(s)",
"select-token-to-receive": "Select token to receive",
@@ -2248,6 +2272,7 @@
"set-up-sync": "Set up sync",
"settings": "Settings",
"setup-group-chat": "Setup group chat",
"setup-keycard": "Setup Keycard",
"setup-syncing": "Pair devices to sync",
"share": "Share",
"share-account": "Share account",
@@ -2472,6 +2497,7 @@
"time-in-mins": "{{minutes}} min",
"timeline": "Timeline",
"tip-cap": "Tip cap",
"tips-scan-keycard": "Tips to scan your Keycard",
"to": "to",
"to-block": "Block",
"to-capitalized": "To",
@@ -2486,8 +2512,8 @@
"token-auto-validate-name-error": "Wrong name for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}",
"token-auto-validate-symbol-error": "Wrong symbol for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}",
"token-details": "Token details",
"token-gated-communities": "Token gated communities",
"token-gated-communities-info": "Here will be something relevant about this topic. This will help the user get more context and therefore have a better understanding of it.",
"token-gated-communities": "Token-gated communities",
"token-gated-communities-info": "If you want to join a token-gated community, you need to meet the token requirements set by the community Owner.\n\nThe Status app automatically checks if you meet the community requirements. If you meet the requirements, the option to join the community is available.",
"token-master": "Token Master",
"token-not-available-on-networks": "{{token-symbol}} is not available on {{networks}}.",
"token-not-available-on-receiver-networks": "{{token-symbol}} is not available on the recipients desired networks. Proceed with caution.",
@@ -2626,6 +2652,7 @@
"use-as-profile-picture": "Use as profile picture",
"use-biometrics": "Use biometrics to fill in your password",
"use-keycard": "Use Keycard",
"use-keycard-login-sign": "Use this Keycard to login and sign transactions",
"use-keycard-subtitle": "Keys will be stored on your Keycard",
"use-photo": "Use Photo",
"use-recovery-phrase": "Use recovery phrase",
@@ -2770,6 +2797,7 @@
"what-are-you-waiting-for": "What are you waiting for?",
"what-changed": "What changed",
"what-is-shared": "What is shared",
"what-to-do": "What you would like to do?",
"what-we-will-receive": "What we will receive:",
"what-we-wont-receive": "What we won't receive:",
"whats-on-your-mind": "Whats on your mind…",