Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6397a7fdd8 | ||
|
|
66f177370a | ||
|
|
ad7d58788f | ||
|
|
ad4ea0783f | ||
|
|
5c44cb6399 | ||
|
|
a9e0b3dd6b | ||
|
|
131da5f444 | ||
|
|
874ececc47 | ||
|
|
5f1d74a77a | ||
|
|
00b0d75fc4 | ||
|
|
4b93c8ff43 | ||
|
|
80ad2b8fca | ||
|
|
a8bc93eb17 | ||
|
|
9d3fc4286e | ||
|
|
16fba373c9 | ||
|
|
9c3ff8c88c | ||
|
|
2525620730 | ||
|
|
6dde1d7b74 | ||
|
|
1691e2fe34 | ||
|
|
b536d20713 | ||
|
|
ca3720be0a | ||
|
|
63d0aaca2a |
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
@@ -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 ];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
'';
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"})
|
||||
|
||||
+17
-19
@@ -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)]])
|
||||
@@ -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 can’t 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 can’t use it right now"}
|
||||
:keycard/error.keycard-locked {:title "Keycard is locked"
|
||||
:description "You can’t 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."]]]))
|
||||
@@ -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])]})))
|
||||
|
||||
+7
-9
@@ -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 []
|
||||
+6
-5
@@ -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. Don’t 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,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
|
||||
%])}]]}]]]})))
|
||||
|
||||
@@ -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 %)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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}}]
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -36,3 +36,8 @@
|
||||
(fn [keycard]
|
||||
(:connection-sheet-opts keycard)))
|
||||
|
||||
(rf/reg-sub
|
||||
:keycard/application-info-error
|
||||
:<- [:keycard]
|
||||
(fn [keycard]
|
||||
(:application-info-error keycard)))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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"
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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, something’s 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, something’s 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()
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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. Don’t 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; you’ll 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, it’s 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 pair’s 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 won’t 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 isn’t 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": "Let’s see what’s 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 recipient’s 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": "What’s on your mind…",
|
||||
|
||||
Reference in New Issue
Block a user